authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2025-09-19 17:45:43-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-30 18:28:47-07:00
logf50c6479774d49fc21c5652b39bcad3c2512867b
tree4c27a9c71489f778323b2decccd9607007da4e19
parente79a00adf664ef46b74da5ed2d620d342b9f8807

add deflate compression, simplify decompression

Implements deflate compression from scratch. A history window is kept in the writer's buffer for matching and a chained hash table is used to find matches. Tokens are accumulated until a threshold is reached and then outputted as a block. Flush is used to indicate end of stream. Additionally, two other deflate writers are provided: * `Raw` writes only in store blocks (the uncompressed bytes). It utilizes data vectors to efficiently send block headers and data. * `Huffman` only performs Huffman compression on data and no matching. The above are also able to take advantage of writer semantics since they do not need to keep a history. Literal and distance code parameters in `token` have also been reworked. Their parameters are now derived mathematically, however the more expensive ones are still obtained through a lookup table (expect on ReleaseSmall). Decompression bit reading has been greatly simplified, taking advantage of the ability to peek on the underlying reader. Additionally, a few bugs with limit handling have been fixed.

8 files changed, 2930 insertions(+), 2033 deletions(-)

lib/std/compress/flate.zig+9-20
......@@ -1,8 +1,7 @@
11const std = @import("../std.zig");
22
3/// When decompressing, the output buffer is used as the history window, so
4/// less than this may result in failure to decompress streams that were
5/// compressed with a larger window.
3/// When compressing and decompressing, the provided buffer is used as the
4/// history window, so it must be at least this size.
65pub const max_window_len = history_len * 2;
76
87pub const history_len = 32768;
......@@ -15,10 +14,6 @@ pub const Compress = @import("flate/Compress.zig");
1514/// produces the original full-size data.
1615pub const Decompress = @import("flate/Decompress.zig");
1716
18/// Compression without Lempel-Ziv match searching. Faster compression, less
19/// memory requirements but bigger compressed sizes.
20pub const HuffmanEncoder = @import("flate/HuffmanEncoder.zig");
21
2217/// Container of the deflate bit stream body. Container adds header before
2318/// deflate bit stream and footer after. It can bi gzip, zlib or raw (no header,
2419/// no footer, raw bit stream).
......@@ -112,28 +107,24 @@ pub const Container = enum {
112107 switch (h.*) {
113108 .raw => {},
114109 .gzip => |*gzip| {
115 gzip.update(buf);
116 gzip.count +%= buf.len;
110 gzip.crc.update(buf);
111 gzip.count +%= @truncate(buf.len);
117112 },
118113 .zlib => |*zlib| {
119114 zlib.update(buf);
120115 },
121 inline .gzip, .zlib => |*x| x.update(buf),
122116 }
123117 }
124118
125119 pub fn writeFooter(hasher: *Hasher, writer: *std.Io.Writer) std.Io.Writer.Error!void {
126 var bits: [4]u8 = undefined;
127120 switch (hasher.*) {
128121 .gzip => |*gzip| {
129122 // GZIP 8 bytes footer
130123 // - 4 bytes, CRC32 (CRC-32)
131 // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32
132 std.mem.writeInt(u32, &bits, gzip.final(), .little);
133 try writer.writeAll(&bits);
134
135 std.mem.writeInt(u32, &bits, gzip.bytes_read, .little);
136 try writer.writeAll(&bits);
124 // - 4 bytes, ISIZE (Input SIZE) - size of the original
125 // (uncompressed) input data modulo 2^32
126 try writer.writeInt(u32, gzip.crc.final(), .little);
127 try writer.writeInt(u32, gzip.count, .little);
137128 },
138129 .zlib => |*zlib| {
139130 // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
......@@ -141,8 +132,7 @@ pub const Container = enum {
141132 // Checksum value of the uncompressed data (excluding any
142133 // dictionary data) computed according to Adler-32
143134 // algorithm.
144 std.mem.writeInt(u32, &bits, zlib.final, .big);
145 try writer.writeAll(&bits);
135 try writer.writeInt(u32, zlib.adler, .big);
146136 },
147137 .raw => {},
148138 }
......@@ -174,7 +164,6 @@ pub const Container = enum {
174164};
175165
176166test {
177 _ = HuffmanEncoder;
178167 _ = Compress;
179168 _ = Decompress;
180169}
lib/std/compress/flate/BlockWriter.zig deleted-591
......@@ -1,591 +0,0 @@
1//! Accepts list of tokens, decides what is best block type to write. What block
2//! type will provide best compression. Writes header and body of the block.
3const std = @import("std");
4const assert = std.debug.assert;
5const Writer = std.Io.Writer;
6
7const BlockWriter = @This();
8const flate = @import("../flate.zig");
9const Compress = flate.Compress;
10const HuffmanEncoder = flate.HuffmanEncoder;
11const Token = @import("Token.zig");
12
13const codegen_order = HuffmanEncoder.codegen_order;
14const end_code_mark = 255;
15
16output: *Writer,
17
18codegen_freq: [HuffmanEncoder.codegen_code_count]u16,
19literal_freq: [HuffmanEncoder.max_num_lit]u16,
20distance_freq: [HuffmanEncoder.distance_code_count]u16,
21codegen: [HuffmanEncoder.max_num_lit + HuffmanEncoder.distance_code_count + 1]u8,
22literal_encoding: HuffmanEncoder,
23distance_encoding: HuffmanEncoder,
24codegen_encoding: HuffmanEncoder,
25fixed_literal_encoding: HuffmanEncoder,
26fixed_distance_encoding: HuffmanEncoder,
27huff_distance: HuffmanEncoder,
28
29fixed_literal_codes: [HuffmanEncoder.max_num_frequencies]HuffmanEncoder.Code,
30fixed_distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code,
31distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code,
32
33pub fn init(output: *Writer) BlockWriter {
34 return .{
35 .output = output,
36 .codegen_freq = undefined,
37 .literal_freq = undefined,
38 .distance_freq = undefined,
39 .codegen = undefined,
40 .literal_encoding = undefined,
41 .distance_encoding = undefined,
42 .codegen_encoding = undefined,
43 .fixed_literal_encoding = undefined,
44 .fixed_distance_encoding = undefined,
45 .huff_distance = undefined,
46 .fixed_literal_codes = undefined,
47 .fixed_distance_codes = undefined,
48 .distance_codes = undefined,
49 };
50}
51
52pub fn initBuffers(bw: *BlockWriter) void {
53 bw.fixed_literal_encoding = .fixedLiteralEncoder(&bw.fixed_literal_codes);
54 bw.fixed_distance_encoding = .fixedDistanceEncoder(&bw.fixed_distance_codes);
55 bw.huff_distance = .huffmanDistanceEncoder(&bw.distance_codes);
56}
57
58/// Flush intrenal bit buffer to the writer.
59/// Should be called only when bit stream is at byte boundary.
60///
61/// That is after final block; when last byte could be incomplete or
62/// after stored block; which is aligned to the byte boundary (it has x
63/// padding bits after first 3 bits).
64pub fn flush(self: *BlockWriter) Writer.Error!void {
65 try self.bit_writer.flush();
66}
67
68fn writeCode(self: *BlockWriter, c: Compress.HuffCode) Writer.Error!void {
69 try self.bit_writer.writeBits(c.code, c.len);
70}
71
72/// RFC 1951 3.2.7 specifies a special run-length encoding for specifying
73/// the literal and distance lengths arrays (which are concatenated into a single
74/// array). This method generates that run-length encoding.
75///
76/// The result is written into the codegen array, and the frequencies
77/// of each code is written into the codegen_freq array.
78/// Codes 0-15 are single byte codes. Codes 16-18 are followed by additional
79/// information. Code bad_code is an end marker
80///
81/// num_literals: The number of literals in literal_encoding
82/// num_distances: The number of distances in distance_encoding
83/// lit_enc: The literal encoder to use
84/// dist_enc: The distance encoder to use
85fn generateCodegen(
86 self: *BlockWriter,
87 num_literals: u32,
88 num_distances: u32,
89 lit_enc: *Compress.LiteralEncoder,
90 dist_enc: *Compress.DistanceEncoder,
91) void {
92 for (self.codegen_freq, 0..) |_, i| {
93 self.codegen_freq[i] = 0;
94 }
95
96 // Note that we are using codegen both as a temporary variable for holding
97 // a copy of the frequencies, and as the place where we put the result.
98 // This is fine because the output is always shorter than the input used
99 // so far.
100 var codegen = &self.codegen; // cache
101 // Copy the concatenated code sizes to codegen. Put a marker at the end.
102 var cgnl = codegen[0..num_literals];
103 for (cgnl, 0..) |_, i| {
104 cgnl[i] = @as(u8, @intCast(lit_enc.codes[i].len));
105 }
106
107 cgnl = codegen[num_literals .. num_literals + num_distances];
108 for (cgnl, 0..) |_, i| {
109 cgnl[i] = @as(u8, @intCast(dist_enc.codes[i].len));
110 }
111 codegen[num_literals + num_distances] = end_code_mark;
112
113 var size = codegen[0];
114 var count: i32 = 1;
115 var out_index: u32 = 0;
116 var in_index: u32 = 1;
117 while (size != end_code_mark) : (in_index += 1) {
118 // INVARIANT: We have seen "count" copies of size that have not yet
119 // had output generated for them.
120 const next_size = codegen[in_index];
121 if (next_size == size) {
122 count += 1;
123 continue;
124 }
125 // We need to generate codegen indicating "count" of size.
126 if (size != 0) {
127 codegen[out_index] = size;
128 out_index += 1;
129 self.codegen_freq[size] += 1;
130 count -= 1;
131 while (count >= 3) {
132 var n: i32 = 6;
133 if (n > count) {
134 n = count;
135 }
136 codegen[out_index] = 16;
137 out_index += 1;
138 codegen[out_index] = @as(u8, @intCast(n - 3));
139 out_index += 1;
140 self.codegen_freq[16] += 1;
141 count -= n;
142 }
143 } else {
144 while (count >= 11) {
145 var n: i32 = 138;
146 if (n > count) {
147 n = count;
148 }
149 codegen[out_index] = 18;
150 out_index += 1;
151 codegen[out_index] = @as(u8, @intCast(n - 11));
152 out_index += 1;
153 self.codegen_freq[18] += 1;
154 count -= n;
155 }
156 if (count >= 3) {
157 // 3 <= count <= 10
158 codegen[out_index] = 17;
159 out_index += 1;
160 codegen[out_index] = @as(u8, @intCast(count - 3));
161 out_index += 1;
162 self.codegen_freq[17] += 1;
163 count = 0;
164 }
165 }
166 count -= 1;
167 while (count >= 0) : (count -= 1) {
168 codegen[out_index] = size;
169 out_index += 1;
170 self.codegen_freq[size] += 1;
171 }
172 // Set up invariant for next time through the loop.
173 size = next_size;
174 count = 1;
175 }
176 // Marker indicating the end of the codegen.
177 codegen[out_index] = end_code_mark;
178}
179
180const DynamicSize = struct {
181 size: u32,
182 num_codegens: u32,
183};
184
185/// dynamicSize returns the size of dynamically encoded data in bits.
186fn dynamicSize(
187 self: *BlockWriter,
188 lit_enc: *Compress.LiteralEncoder, // literal encoder
189 dist_enc: *Compress.DistanceEncoder, // distance encoder
190 extra_bits: u32,
191) DynamicSize {
192 var num_codegens = self.codegen_freq.len;
193 while (num_codegens > 4 and self.codegen_freq[codegen_order[num_codegens - 1]] == 0) {
194 num_codegens -= 1;
195 }
196 const header = 3 + 5 + 5 + 4 + (3 * num_codegens) +
197 self.codegen_encoding.bitLength(self.codegen_freq[0..]) +
198 self.codegen_freq[16] * 2 +
199 self.codegen_freq[17] * 3 +
200 self.codegen_freq[18] * 7;
201 const size = header +
202 lit_enc.bitLength(&self.literal_freq) +
203 dist_enc.bitLength(&self.distance_freq) +
204 extra_bits;
205
206 return DynamicSize{
207 .size = @as(u32, @intCast(size)),
208 .num_codegens = @as(u32, @intCast(num_codegens)),
209 };
210}
211
212/// fixedSize returns the size of dynamically encoded data in bits.
213fn fixedSize(self: *BlockWriter, extra_bits: u32) u32 {
214 return 3 +
215 self.fixed_literal_encoding.bitLength(&self.literal_freq) +
216 self.fixed_distance_encoding.bitLength(&self.distance_freq) +
217 extra_bits;
218}
219
220const StoredSize = struct {
221 size: u32,
222 storable: bool,
223};
224
225/// storedSizeFits calculates the stored size, including header.
226/// The function returns the size in bits and whether the block
227/// fits inside a single block.
228fn storedSizeFits(in: ?[]const u8) StoredSize {
229 if (in == null) {
230 return .{ .size = 0, .storable = false };
231 }
232 if (in.?.len <= HuffmanEncoder.max_store_block_size) {
233 return .{ .size = @as(u32, @intCast((in.?.len + 5) * 8)), .storable = true };
234 }
235 return .{ .size = 0, .storable = false };
236}
237
238/// Write the header of a dynamic Huffman block to the output stream.
239///
240/// num_literals: The number of literals specified in codegen
241/// num_distances: The number of distances specified in codegen
242/// num_codegens: The number of codegens used in codegen
243/// eof: Is it the end-of-file? (end of stream)
244fn dynamicHeader(
245 self: *BlockWriter,
246 num_literals: u32,
247 num_distances: u32,
248 num_codegens: u32,
249 eof: bool,
250) Writer.Error!void {
251 const first_bits: u32 = if (eof) 5 else 4;
252 try self.bit_writer.writeBits(first_bits, 3);
253 try self.bit_writer.writeBits(num_literals - 257, 5);
254 try self.bit_writer.writeBits(num_distances - 1, 5);
255 try self.bit_writer.writeBits(num_codegens - 4, 4);
256
257 var i: u32 = 0;
258 while (i < num_codegens) : (i += 1) {
259 const value = self.codegen_encoding.codes[codegen_order[i]].len;
260 try self.bit_writer.writeBits(value, 3);
261 }
262
263 i = 0;
264 while (true) {
265 const code_word: u32 = @as(u32, @intCast(self.codegen[i]));
266 i += 1;
267 if (code_word == end_code_mark) {
268 break;
269 }
270 try self.writeCode(self.codegen_encoding.codes[@as(u32, @intCast(code_word))]);
271
272 switch (code_word) {
273 16 => {
274 try self.bit_writer.writeBits(self.codegen[i], 2);
275 i += 1;
276 },
277 17 => {
278 try self.bit_writer.writeBits(self.codegen[i], 3);
279 i += 1;
280 },
281 18 => {
282 try self.bit_writer.writeBits(self.codegen[i], 7);
283 i += 1;
284 },
285 else => {},
286 }
287 }
288}
289
290fn storedHeader(self: *BlockWriter, length: usize, eof: bool) Writer.Error!void {
291 assert(length <= 65535);
292 const flag: u32 = if (eof) 1 else 0;
293 try self.bit_writer.writeBits(flag, 3);
294 try self.flush();
295 const l: u16 = @intCast(length);
296 try self.bit_writer.writeBits(l, 16);
297 try self.bit_writer.writeBits(~l, 16);
298}
299
300fn fixedHeader(self: *BlockWriter, eof: bool) Writer.Error!void {
301 // Indicate that we are a fixed Huffman block
302 var value: u32 = 2;
303 if (eof) {
304 value = 3;
305 }
306 try self.bit_writer.writeBits(value, 3);
307}
308
309/// Write a block of tokens with the smallest encoding. Will choose block type.
310/// The original input can be supplied, and if the huffman encoded data
311/// is larger than the original bytes, the data will be written as a
312/// stored block.
313/// If the input is null, the tokens will always be Huffman encoded.
314pub fn write(self: *BlockWriter, tokens: []const Token, eof: bool, input: ?[]const u8) Writer.Error!void {
315 const lit_and_dist = self.indexTokens(tokens);
316 const num_literals = lit_and_dist.num_literals;
317 const num_distances = lit_and_dist.num_distances;
318
319 var extra_bits: u32 = 0;
320 const ret = storedSizeFits(input);
321 const stored_size = ret.size;
322 const storable = ret.storable;
323
324 if (storable) {
325 // We only bother calculating the costs of the extra bits required by
326 // the length of distance fields (which will be the same for both fixed
327 // and dynamic encoding), if we need to compare those two encodings
328 // against stored encoding.
329 var length_code: u16 = Token.length_codes_start + 8;
330 while (length_code < num_literals) : (length_code += 1) {
331 // First eight length codes have extra size = 0.
332 extra_bits += @as(u32, @intCast(self.literal_freq[length_code])) *
333 @as(u32, @intCast(Token.lengthExtraBits(length_code)));
334 }
335 var distance_code: u16 = 4;
336 while (distance_code < num_distances) : (distance_code += 1) {
337 // First four distance codes have extra size = 0.
338 extra_bits += @as(u32, @intCast(self.distance_freq[distance_code])) *
339 @as(u32, @intCast(Token.distanceExtraBits(distance_code)));
340 }
341 }
342
343 // Figure out smallest code.
344 // Fixed Huffman baseline.
345 var literal_encoding = &self.fixed_literal_encoding;
346 var distance_encoding = &self.fixed_distance_encoding;
347 var size = self.fixedSize(extra_bits);
348
349 // Dynamic Huffman?
350 var num_codegens: u32 = 0;
351
352 // Generate codegen and codegenFrequencies, which indicates how to encode
353 // the literal_encoding and the distance_encoding.
354 self.generateCodegen(
355 num_literals,
356 num_distances,
357 &self.literal_encoding,
358 &self.distance_encoding,
359 );
360 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
361 const dynamic_size = self.dynamicSize(
362 &self.literal_encoding,
363 &self.distance_encoding,
364 extra_bits,
365 );
366 const dyn_size = dynamic_size.size;
367 num_codegens = dynamic_size.num_codegens;
368
369 if (dyn_size < size) {
370 size = dyn_size;
371 literal_encoding = &self.literal_encoding;
372 distance_encoding = &self.distance_encoding;
373 }
374
375 // Stored bytes?
376 if (storable and stored_size < size) {
377 try self.storedBlock(input.?, eof);
378 return;
379 }
380
381 // Huffman.
382 if (@intFromPtr(literal_encoding) == @intFromPtr(&self.fixed_literal_encoding)) {
383 try self.fixedHeader(eof);
384 } else {
385 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
386 }
387
388 // Write the tokens.
389 try self.writeTokens(tokens, &literal_encoding.codes, &distance_encoding.codes);
390}
391
392pub fn storedBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void {
393 try self.storedHeader(input.len, eof);
394 try self.bit_writer.writeBytes(input);
395}
396
397/// writeBlockDynamic encodes a block using a dynamic Huffman table.
398/// This should be used if the symbols used have a disproportionate
399/// histogram distribution.
400/// If input is supplied and the compression savings are below 1/16th of the
401/// input size the block is stored.
402fn dynamicBlock(
403 self: *BlockWriter,
404 tokens: []const Token,
405 eof: bool,
406 input: ?[]const u8,
407) Writer.Error!void {
408 const total_tokens = self.indexTokens(tokens);
409 const num_literals = total_tokens.num_literals;
410 const num_distances = total_tokens.num_distances;
411
412 // Generate codegen and codegenFrequencies, which indicates how to encode
413 // the literal_encoding and the distance_encoding.
414 self.generateCodegen(
415 num_literals,
416 num_distances,
417 &self.literal_encoding,
418 &self.distance_encoding,
419 );
420 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
421 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.distance_encoding, 0);
422 const size = dynamic_size.size;
423 const num_codegens = dynamic_size.num_codegens;
424
425 // Store bytes, if we don't get a reasonable improvement.
426
427 const stored_size = storedSizeFits(input);
428 const ssize = stored_size.size;
429 const storable = stored_size.storable;
430 if (storable and ssize < (size + (size >> 4))) {
431 try self.storedBlock(input.?, eof);
432 return;
433 }
434
435 // Write Huffman table.
436 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
437
438 // Write the tokens.
439 try self.writeTokens(tokens, &self.literal_encoding.codes, &self.distance_encoding.codes);
440}
441
442const TotalIndexedTokens = struct {
443 num_literals: u32,
444 num_distances: u32,
445};
446
447/// Indexes a slice of tokens followed by an end_block_marker, and updates
448/// literal_freq and distance_freq, and generates literal_encoding
449/// and distance_encoding.
450/// The number of literal and distance tokens is returned.
451fn indexTokens(self: *BlockWriter, tokens: []const Token) TotalIndexedTokens {
452 var num_literals: u32 = 0;
453 var num_distances: u32 = 0;
454
455 for (self.literal_freq, 0..) |_, i| {
456 self.literal_freq[i] = 0;
457 }
458 for (self.distance_freq, 0..) |_, i| {
459 self.distance_freq[i] = 0;
460 }
461
462 for (tokens) |t| {
463 if (t.kind == Token.Kind.literal) {
464 self.literal_freq[t.literal()] += 1;
465 continue;
466 }
467 self.literal_freq[t.lengthCode()] += 1;
468 self.distance_freq[t.distanceCode()] += 1;
469 }
470 // add end_block_marker token at the end
471 self.literal_freq[HuffmanEncoder.end_block_marker] += 1;
472
473 // get the number of literals
474 num_literals = @as(u32, @intCast(self.literal_freq.len));
475 while (self.literal_freq[num_literals - 1] == 0) {
476 num_literals -= 1;
477 }
478 // get the number of distances
479 num_distances = @as(u32, @intCast(self.distance_freq.len));
480 while (num_distances > 0 and self.distance_freq[num_distances - 1] == 0) {
481 num_distances -= 1;
482 }
483 if (num_distances == 0) {
484 // We haven't found a single match. If we want to go with the dynamic encoding,
485 // we should count at least one distance to be sure that the distance huffman tree could be encoded.
486 self.distance_freq[0] = 1;
487 num_distances = 1;
488 }
489 self.literal_encoding.generate(&self.literal_freq, 15);
490 self.distance_encoding.generate(&self.distance_freq, 15);
491 return TotalIndexedTokens{
492 .num_literals = num_literals,
493 .num_distances = num_distances,
494 };
495}
496
497/// Writes a slice of tokens to the output followed by and end_block_marker.
498/// codes for literal and distance encoding must be supplied.
499fn writeTokens(
500 self: *BlockWriter,
501 tokens: []const Token,
502 le_codes: []Compress.HuffCode,
503 oe_codes: []Compress.HuffCode,
504) Writer.Error!void {
505 for (tokens) |t| {
506 if (t.kind == Token.Kind.literal) {
507 try self.writeCode(le_codes[t.literal()]);
508 continue;
509 }
510
511 // Write the length
512 const le = t.lengthEncoding();
513 try self.writeCode(le_codes[le.code]);
514 if (le.extra_bits > 0) {
515 try self.bit_writer.writeBits(le.extra_length, le.extra_bits);
516 }
517
518 // Write the distance
519 const oe = t.distanceEncoding();
520 try self.writeCode(oe_codes[oe.code]);
521 if (oe.extra_bits > 0) {
522 try self.bit_writer.writeBits(oe.extra_distance, oe.extra_bits);
523 }
524 }
525 // add end_block_marker at the end
526 try self.writeCode(le_codes[HuffmanEncoder.end_block_marker]);
527}
528
529/// Encodes a block of bytes as either Huffman encoded literals or uncompressed bytes
530/// if the results only gains very little from compression.
531pub fn huffmanBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void {
532 // Add everything as literals
533 histogram(input, &self.literal_freq);
534
535 self.literal_freq[HuffmanEncoder.end_block_marker] = 1;
536
537 const num_literals = HuffmanEncoder.end_block_marker + 1;
538 self.distance_freq[0] = 1;
539 const num_distances = 1;
540
541 self.literal_encoding.generate(&self.literal_freq, 15);
542
543 // Figure out smallest code.
544 // Always use dynamic Huffman or Store
545 var num_codegens: u32 = 0;
546
547 // Generate codegen and codegenFrequencies, which indicates how to encode
548 // the literal_encoding and the distance_encoding.
549 self.generateCodegen(
550 num_literals,
551 num_distances,
552 &self.literal_encoding,
553 &self.huff_distance,
554 );
555 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
556 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.huff_distance, 0);
557 const size = dynamic_size.size;
558 num_codegens = dynamic_size.num_codegens;
559
560 // Store bytes, if we don't get a reasonable improvement.
561 const stored_size_ret = storedSizeFits(input);
562 const ssize = stored_size_ret.size;
563 const storable = stored_size_ret.storable;
564
565 if (storable and ssize < (size + (size >> 4))) {
566 try self.storedBlock(input, eof);
567 return;
568 }
569
570 // Huffman.
571 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
572 const encoding = self.literal_encoding.codes[0..257];
573
574 for (input) |t| {
575 const c = encoding[t];
576 try self.bit_writer.writeBits(c.code, c.len);
577 }
578 try self.writeCode(encoding[HuffmanEncoder.end_block_marker]);
579}
580
581fn histogram(b: []const u8, h: *[286]u16) void {
582 // Clear histogram
583 for (h, 0..) |_, i| {
584 h[i] = 0;
585 }
586
587 var lh = h.*[0..256];
588 for (b) |t| {
589 lh[t] += 1;
590 }
591}
lib/std/compress/flate/Compress.zig+2479-261
......@@ -1,332 +1,2550 @@
1//! Default compression algorithm. Has two steps: tokenization and token
2//! encoding.
1//! Allocates statically ~224K (128K lookup, 96K tokens).
32//!
4//! Tokenization takes uncompressed input stream and produces list of tokens.
5//! Each token can be literal (byte of data) or match (backrefernce to previous
6//! data with length and distance). Tokenization accumulators 32K tokens, when
7//! full or `flush` is called tokens are passed to the `block_writer`. Level
8//! defines how hard (how slow) it tries to find match.
9//!
10//! Block writer will decide which type of deflate block to write (stored, fixed,
11//! dynamic) and encode tokens to the output byte stream. Client has to call
12//! `finish` to write block with the final bit set.
13//!
14//! Container defines type of header and footer which can be gzip, zlib or raw.
15//! They all share same deflate body. Raw has no header or footer just deflate
16//! body.
17//!
18//! Compression algorithm explained in rfc-1951 (slightly edited for this case):
19//!
20//! The compressor uses a chained hash table `lookup` to find duplicated
21//! strings, using a hash function that operates on 4-byte sequences. At any
22//! given point during compression, let XYZW be the next 4 input bytes
23//! (lookahead) to be examined (not necessarily all different, of course).
24//! First, the compressor examines the hash chain for XYZW. If the chain is
25//! empty, the compressor simply writes out X as a literal byte and advances
26//! one byte in the input. If the hash chain is not empty, indicating that the
27//! sequence XYZW (or, if we are unlucky, some other 4 bytes with the same
28//! hash function value) has occurred recently, the compressor compares all
29//! strings on the XYZW hash chain with the actual input data sequence
30//! starting at the current point, and selects the longest match.
31//!
32//! To improve overall compression, the compressor defers the selection of
33//! matches ("lazy matching"): after a match of length N has been found, the
34//! compressor searches for a longer match starting at the next input byte. If
35//! it finds a longer match, it truncates the previous match to a length of
36//! one (thus producing a single literal byte) and then emits the longer
37//! match. Otherwise, it emits the original match, and, as described above,
38//! advances N bytes before continuing.
39//!
40//!
41//! Allocates statically ~400K (192K lookup, 128K tokens, 64K window).
3//! The source of an `error.WriteFailed` is always the backing writer. After an
4//! `error.WriteFailed`, the `.writer` becomes `.failing` and is unrecoverable.
5//! After a `flush`, the writer also becomes `.failing` since the stream has
6//! been finished. This behavior also applies to `Raw` and `Huffman`.
7
8// Implementation details:
9// A chained hash table is used to find matches. `drain` always preserves `flate.history_len`
10// bytes to use as a history and avoids tokenizing the final bytes since they can be part of
11// a longer match with unwritten bytes (unless it is a `flush`). The minimum match searched
12// for is of length `seq_bytes`. If a match is made, a longer match is also checked for at
13// the next byte (lazy matching) if the last match does not meet the `Options.lazy` threshold.
14//
15// Up to `block_token` tokens are accumalated in `buffered_tokens` and are outputted in
16// `write_block` which determines the optimal block type and frequencies.
4217
4318const builtin = @import("builtin");
4419const std = @import("std");
45const assert = std.debug.assert;
46const testing = std.testing;
47const expect = testing.expect;
4820const mem = std.mem;
4921const math = std.math;
50const Writer = std.Io.Writer;
22const assert = std.debug.assert;
23const Io = std.Io;
24const Writer = Io.Writer;
5125
5226const Compress = @This();
53const Token = @import("Token.zig");
54const BlockWriter = @import("BlockWriter.zig");
27const token = @import("token.zig");
5528const flate = @import("../flate.zig");
56const Container = flate.Container;
57const Lookup = @import("Lookup.zig");
58const HuffmanEncoder = flate.HuffmanEncoder;
59const LiteralNode = HuffmanEncoder.LiteralNode;
60
61lookup: Lookup = .{},
62tokens: Tokens = .{},
63block_writer: BlockWriter,
64level: LevelArgs,
65hasher: Container.Hasher,
66writer: Writer,
67state: State,
6829
69// Match and literal at the previous position.
70// Used for lazy match finding in processWindow.
71prev_match: ?Token = null,
72prev_literal: ?u8 = null,
30/// Until #104 is implemented, a ?u15 takes 4 bytes, which is unacceptable
31/// as it doubles the size of this already massive structure.
32///
33/// Also, there are no `to` / `from` methods because LLVM 21 does not
34/// optimize away the conversion from and to `?u15`.
35const PackedOptionalU15 = packed struct(u16) {
36 value: u15,
37 is_null: bool,
7338
74pub const State = enum { header, middle, ended };
39 pub fn int(p: PackedOptionalU15) u16 {
40 return @bitCast(p);
41 }
7542
76/// Trades between speed and compression size.
77/// Starts with level 4: in [zlib](https://github.com/madler/zlib/blob/abd3d1a28930f89375d4b41408b39f6c1be157b2/deflate.c#L115C1-L117C43)
78/// levels 1-3 are using different algorithm to perform faster but with less
79/// compression. That is not implemented here.
80pub const Level = enum(u4) {
81 level_4 = 4,
82 level_5 = 5,
83 level_6 = 6,
84 level_7 = 7,
85 level_8 = 8,
86 level_9 = 9,
87
88 fast = 0xb,
89 default = 0xc,
90 best = 0xd,
43 pub const null_bit: PackedOptionalU15 = .{ .value = 0, .is_null = true };
9144};
9245
93/// Number of tokens to accumulate in deflate before starting block encoding.
94///
95/// In zlib this depends on memlevel: 6 + memlevel, where default memlevel is
96/// 8 and max 9 that gives 14 or 15 bits.
97pub const n_tokens = 1 << 15;
98
99/// Algorithm knobs for each level.
100const LevelArgs = struct {
101 good: u16, // Do less lookups if we already have match of this length.
102 nice: u16, // Stop looking for better match if we found match with at least this length.
103 lazy: u16, // Don't do lazy match find if got match with at least this length.
104 chain: u16, // How many lookups for previous match to perform.
105
106 pub fn get(level: Level) LevelArgs {
107 return switch (level) {
108 .fast, .level_4 => .{ .good = 4, .lazy = 4, .nice = 16, .chain = 16 },
109 .level_5 => .{ .good = 8, .lazy = 16, .nice = 32, .chain = 32 },
110 .default, .level_6 => .{ .good = 8, .lazy = 16, .nice = 128, .chain = 128 },
111 .level_7 => .{ .good = 8, .lazy = 32, .nice = 128, .chain = 256 },
112 .level_8 => .{ .good = 32, .lazy = 128, .nice = 258, .chain = 1024 },
113 .best, .level_9 => .{ .good = 32, .lazy = 258, .nice = 258, .chain = 4096 },
46/// After `flush` is called, all vtable calls with result in `error.WriteFailed.`
47writer: Writer,
48has_history: bool,
49bit_writer: BitWriter,
50buffered_tokens: struct {
51 /// List of `TokenBufferEntryHeader`s and their trailing data.
52 list: [@as(usize, block_tokens) * 3]u8,
53 pos: u32,
54 n: u16,
55 lit_freqs: [286]u16,
56 dist_freqs: [30]u16,
57
58 pub const empty: @This() = .{
59 .list = undefined,
60 .pos = 0,
61 .n = 0,
62 .lit_freqs = @splat(0),
63 .dist_freqs = @splat(0),
64 };
65},
66lookup: struct {
67 /// Indexes are the hashes of four-bytes sequences.
68 ///
69 /// Values are the positions in `chain` of the previous four bytes with the same hash.
70 head: [1 << lookup_hash_bits]PackedOptionalU15,
71 /// Values are the non-zero number of bytes backwards in the history with the same hash.
72 ///
73 /// The relationship of chain indexes and bytes relative to the latest history byte is
74 /// `chain_pos -% chain_index = history_index`.
75 chain: [32768]PackedOptionalU15,
76 /// The index in `chain` which is of the newest byte of the history.
77 chain_pos: u15,
78},
79container: flate.Container,
80hasher: flate.Container.Hasher,
81opts: Options,
82
83const BitWriter = struct {
84 output: *Writer,
85 buffered: u7,
86 buffered_n: u3,
87
88 pub fn init(w: *Writer) BitWriter {
89 return .{
90 .output = w,
91 .buffered = 0,
92 .buffered_n = 0,
11493 };
11594 }
95
96 /// Asserts `bits` is zero-extended
97 pub fn write(b: *BitWriter, bits: u56, n: u6) Writer.Error!void {
98 assert(@as(u8, b.buffered) >> b.buffered_n == 0);
99 assert(@as(u57, bits) >> n == 0); // n may be 56 so u57 is needed
100 const combined = @shlExact(@as(u64, bits), b.buffered_n) | b.buffered;
101 const combined_bits = @as(u6, b.buffered_n) + n;
102
103 const out = try b.output.writableSliceGreedy(8);
104 mem.writeInt(u64, out[0..8], combined, .little);
105 b.output.advance(combined_bits / 8);
106
107 b.buffered_n = @truncate(combined_bits);
108 b.buffered = @intCast(combined >> (combined_bits - b.buffered_n));
109 }
110
111 /// Assserts one byte can be written to `b.otuput` without rebasing.
112 pub fn byteAlign(b: *BitWriter) void {
113 b.output.unusedCapacitySlice()[0] = b.buffered;
114 b.output.advance(@intFromBool(b.buffered_n != 0));
115 b.buffered = 0;
116 b.buffered_n = 0;
117 }
118
119 pub fn writeClen(
120 b: *BitWriter,
121 hclen: u4,
122 clen_values: []u8,
123 clen_extra: []u8,
124 clen_codes: [19]u16,
125 clen_bits: [19]u4,
126 ) Writer.Error!void {
127 // Write the first four clen entries seperately since they are always present,
128 // and writing them all at once takes too many bits.
129 try b.write(clen_bits[token.codegen_order[0]] |
130 @shlExact(@as(u6, clen_bits[token.codegen_order[1]]), 3) |
131 @shlExact(@as(u9, clen_bits[token.codegen_order[2]]), 6) |
132 @shlExact(@as(u12, clen_bits[token.codegen_order[3]]), 9), 12);
133
134 var i = hclen;
135 var clen_bits_table: u45 = 0;
136 while (i != 0) {
137 i -= 1;
138 clen_bits_table <<= 3;
139 clen_bits_table |= clen_bits[token.codegen_order[4..][i]];
140 }
141 try b.write(clen_bits_table, @as(u6, hclen) * 3);
142
143 for (clen_values, clen_extra) |value, extra| {
144 try b.write(
145 clen_codes[value] | @shlExact(@as(u16, extra), clen_bits[value]),
146 clen_bits[value] + @as(u3, switch (value) {
147 0...15 => 0,
148 16 => 2,
149 17 => 3,
150 18 => 7,
151 else => unreachable,
152 }),
153 );
154 }
155 }
156};
157
158/// Number of tokens to accumulate before outputing as a block.
159/// The maximum value is `math.maxInt(u16) - 1` since one token is reserved for end-of-block.
160const block_tokens: u16 = 1 << 15;
161const lookup_hash_bits = 15;
162const Hash = u16; // `u[lookup_hash_bits]` is not used due to worse optimization (with LLVM 21)
163const seq_bytes = 3; // not intended to be changed
164const Seq = std.meta.Int(.unsigned, seq_bytes * 8);
165
166const TokenBufferEntryHeader = packed struct(u16) {
167 kind: enum(u1) {
168 /// Followed by non-zero `data` byte literals.
169 bytes,
170 /// Followed by the length as a byte
171 match,
172 },
173 data: u15,
174};
175
176const BlockHeader = packed struct(u3) {
177 final: bool,
178 kind: enum(u2) { stored, fixed, dynamic, _ },
179
180 pub fn int(h: BlockHeader) u3 {
181 return @bitCast(h);
182 }
183
184 pub const Dynamic = packed struct(u17) {
185 regular: BlockHeader,
186 hlit: u5,
187 hdist: u5,
188 hclen: u4,
189
190 pub fn int(h: Dynamic) u17 {
191 return @bitCast(h);
192 }
193 };
116194};
117195
196fn outputMatch(c: *Compress, dist: u15, len: u8) Writer.Error!void {
197 // This must come first. Instead of ensuring a full block is never left buffered,
198 // draining it is defered to allow end of stream to be indicated.
199 if (c.buffered_tokens.n == block_tokens) {
200 @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without
201 try c.writeBlock(false);
202 }
203 const header: TokenBufferEntryHeader = .{ .kind = .match, .data = dist };
204 c.buffered_tokens.list[c.buffered_tokens.pos..][0..2].* = @bitCast(header);
205 c.buffered_tokens.list[c.buffered_tokens.pos + 2] = len;
206 c.buffered_tokens.pos += 3;
207 c.buffered_tokens.n += 1;
208
209 c.buffered_tokens.lit_freqs[@as(usize, 257) + token.LenCode.fromVal(len).toInt()] += 1;
210 c.buffered_tokens.dist_freqs[token.DistCode.fromVal(dist).toInt()] += 1;
211}
212
213fn outputBytes(c: *Compress, bytes: []const u8) Writer.Error!void {
214 var remaining = bytes;
215 while (remaining.len != 0) {
216 if (c.buffered_tokens.n == block_tokens) {
217 @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without
218 try c.writeBlock(false);
219 }
220
221 const n = @min(remaining.len, block_tokens - c.buffered_tokens.n, math.maxInt(u15));
222 assert(n != 0);
223 const header: TokenBufferEntryHeader = .{ .kind = .bytes, .data = n };
224 c.buffered_tokens.list[c.buffered_tokens.pos..][0..2].* = @bitCast(header);
225 @memcpy(c.buffered_tokens.list[c.buffered_tokens.pos + 2 ..][0..n], remaining[0..n]);
226 c.buffered_tokens.pos += @as(u32, 2) + n;
227 c.buffered_tokens.n += n;
228
229 for (remaining[0..n]) |b| {
230 c.buffered_tokens.lit_freqs[b] += 1;
231 }
232 remaining = remaining[n..];
233 }
234}
235
236fn hash(x: u32) Hash {
237 return @intCast((x *% 0x9E3779B1) >> (32 - lookup_hash_bits));
238}
239
240/// Trades between speed and compression size.
241///
242/// Default paramaters are [taken from zlib]
243/// (https://github.com/madler/zlib/blob/v1.3.1/deflate.c#L112)
118244pub const Options = struct {
119 level: Level = .default,
120 container: Container = .raw,
245 /// Perform less lookups when a match of at least this length has been found.
246 good: u16,
247 /// Stop when a match of at least this length has been found.
248 nice: u16,
249 /// Don't attempt a lazy match find when a match of at least this length has been found.
250 lazy: u16,
251 /// Check this many previous locations with the same hash for longer matches.
252 chain: u16,
253
254 // zig fmt: off
255 pub const level_1: Options = .{ .good = 4, .nice = 8, .lazy = 0, .chain = 4 };
256 pub const level_2: Options = .{ .good = 4, .nice = 16, .lazy = 0, .chain = 8 };
257 pub const level_3: Options = .{ .good = 4, .nice = 32, .lazy = 0, .chain = 32 };
258 pub const level_4: Options = .{ .good = 4, .nice = 16, .lazy = 4, .chain = 16 };
259 pub const level_5: Options = .{ .good = 8, .nice = 32, .lazy = 16, .chain = 32 };
260 pub const level_6: Options = .{ .good = 8, .nice = 128, .lazy = 16, .chain = 128 };
261 pub const level_7: Options = .{ .good = 8, .nice = 128, .lazy = 32, .chain = 256 };
262 pub const level_8: Options = .{ .good = 32, .nice = 258, .lazy = 128, .chain = 1024 };
263 pub const level_9: Options = .{ .good = 32, .nice = 258, .lazy = 258, .chain = 4096 };
264 // zig fmt: on
265 pub const fastest = level_1;
266 pub const default = level_6;
267 pub const best = level_9;
121268};
122269
123pub fn init(output: *Writer, buffer: []u8, options: Options) Compress {
270/// It is asserted `buffer` is least `flate.max_history_len` bytes.
271/// It is asserted `output` has a capacity of at least 8 bytes.
272pub fn init(
273 output: *Writer,
274 buffer: []u8,
275 container: flate.Container,
276 opts: Options,
277) Writer.Error!Compress {
278 assert(output.buffer.len > 8);
279 assert(buffer.len >= flate.max_window_len);
280
281 // note that disallowing some of these simplifies matching logic
282 assert(opts.chain != 0); // use `Huffman`, disallowing this simplies matching
283 assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found
284 assert(opts.good <= 258 and opts.nice <= 258); // a longer match will not be found
285 assert(opts.lazy <= opts.nice); // a longer match will (usually) not be found
286 if (opts.good <= opts.lazy) assert(opts.chain >= 1 << 2); // chain can be reduced to zero
287
288 try output.writeAll(container.header());
124289 return .{
125 .block_writer = .init(output),
126 .level = .get(options.level),
127 .hasher = .init(options.container),
128 .state = .header,
129290 .writer = .{
130291 .buffer = buffer,
131 .vtable = &.{ .drain = drain },
292 .vtable = &.{
293 .drain = drain,
294 .flush = flush,
295 .rebase = rebase,
296 },
297 },
298 .has_history = false,
299 .bit_writer = .init(output),
300 .buffered_tokens = .empty,
301 .lookup = .{
302 // init `value` is max so there is 0xff pattern
303 .head = @splat(.{ .value = math.maxInt(u15), .is_null = true }),
304 .chain = undefined,
305 .chain_pos = math.maxInt(u15),
132306 },
307 .container = container,
308 .opts = opts,
309 .hasher = .init(container),
133310 };
134311}
135312
136// Tokens store
137const Tokens = struct {
138 list: [n_tokens]Token = undefined,
139 pos: usize = 0,
313fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
314 errdefer w.* = .failing;
315 // There may have not been enough space in the buffer and the write was sent directly here.
316 // However, it is required that all data goes through the buffer to keep a history.
317 //
318 // Additionally, ensuring the buffer is always full ensures there is always a full history
319 // after.
320 const data_n = w.buffer.len - w.end;
321 _ = w.fixedDrain(data, splat) catch {};
322 assert(w.end == w.buffer.len);
323 try rebaseInner(w, 0, 1, false);
324 return data_n;
325}
326
327fn flush(w: *Writer) Writer.Error!void {
328 defer w.* = .failing;
329 const c: *Compress = @fieldParentPtr("writer", w);
330 try rebaseInner(w, 0, w.buffer.len - flate.history_len, true);
331 try c.bit_writer.output.rebase(0, 1);
332 c.bit_writer.byteAlign();
333 try c.hasher.writeFooter(c.bit_writer.output);
334}
335
336fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
337 return rebaseInner(w, preserve, capacity, false);
338}
339
340pub const rebase_min_preserve = flate.history_len;
341pub const rebase_reserved_capacity = (token.max_length + 1) + seq_bytes;
342
343fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
344 if (!eos) {
345 assert(@max(preserve, rebase_min_preserve) + (capacity + rebase_reserved_capacity) <= w.buffer.len);
346 assert(w.end >= flate.history_len + rebase_reserved_capacity); // Above assert should
347 // fail since rebase is only called when `capacity` is not present. This assertion is
348 // important because a full history is required at the end.
349 } else {
350 assert(preserve == 0 and capacity == w.buffer.len - flate.history_len);
351 }
352
353 const c: *Compress = @fieldParentPtr("writer", w);
354 const buffered = w.buffered();
355
356 const start = @as(usize, flate.history_len) * @intFromBool(c.has_history);
357 const lit_end: usize = if (!eos)
358 buffered.len - rebase_reserved_capacity - (preserve -| flate.history_len)
359 else
360 buffered.len -| (seq_bytes - 1);
361
362 var i = start;
363 var last_unmatched = i;
364 // Read from `w.buffer` instead of `buffered` since the latter may not
365 // have enough bytes. If this is the case, this variable is not used.
366 var seq: Seq = mem.readInt(
367 std.meta.Int(.unsigned, (seq_bytes - 1) * 8),
368 w.buffer[i..][0 .. seq_bytes - 1],
369 .big,
370 );
371 if (buffered[i..].len < seq_bytes - 1) {
372 @branchHint(.unlikely);
373 assert(eos);
374 seq = undefined;
375 assert(i >= lit_end);
376 }
377
378 while (i < lit_end) {
379 var match_start = i;
380 seq <<= 8;
381 seq |= buffered[i + (seq_bytes - 1)];
382 var match = c.matchAndAddHash(i, hash(seq), token.min_length - 1, c.opts.chain, c.opts.good);
383 i += 1;
384 if (match.len < token.min_length) continue;
385
386 var match_unadded = match.len - 1;
387 lazy: {
388 if (match.len >= c.opts.lazy) break :lazy;
389 if (match.len >= c.writer.buffered()[i..].len) {
390 @branchHint(.unlikely); // Only end of stream
391 break :lazy;
392 }
140393
141 fn add(self: *Tokens, t: Token) void {
142 self.list[self.pos] = t;
143 self.pos += 1;
394 var chain = c.opts.chain;
395 var good = c.opts.good;
396 if (match.len >= good) {
397 chain >>= 2;
398 good = math.maxInt(u8); // Reduce only once
399 }
400
401 seq <<= 8;
402 seq |= buffered[i + (seq_bytes - 1)];
403 const lazy = c.matchAndAddHash(i, hash(seq), match.len, chain, good);
404 match_unadded -= 1;
405 i += 1;
406
407 if (lazy.len > match.len) {
408 match_start += 1;
409 match = lazy;
410 match_unadded = match.len - 1;
411 }
412 }
413
414 assert(i + match_unadded == match_start + match.len);
415 assert(mem.eql(
416 u8,
417 buffered[match_start..][0..match.len],
418 buffered[match_start - 1 - match.dist ..][0..match.len],
419 )); // This assert also seems to help codegen.
420
421 try c.outputBytes(buffered[last_unmatched..match_start]);
422 try c.outputMatch(@intCast(match.dist), @intCast(match.len - 3));
423
424 last_unmatched = match_start + match.len;
425 if (last_unmatched + seq_bytes >= w.end) {
426 @branchHint(.unlikely);
427 assert(eos);
428 i = undefined;
429 break;
430 }
431
432 while (true) {
433 seq <<= 8;
434 seq |= buffered[i + (seq_bytes - 1)];
435 _ = c.addHash(i, hash(seq));
436 i += 1;
437
438 match_unadded -= 1;
439 if (match_unadded == 0) break;
440 }
441 assert(i == match_start + match.len);
144442 }
145443
146 fn full(self: *Tokens) bool {
147 return self.pos == self.list.len;
444 if (eos) {
445 i = undefined; // (from match hashing logic)
446 try c.outputBytes(buffered[last_unmatched..]);
447 c.hasher.update(buffered[start..]);
448 try c.writeBlock(true);
449 return;
148450 }
149451
150 fn reset(self: *Tokens) void {
151 self.pos = 0;
452 try c.outputBytes(buffered[last_unmatched..i]);
453 c.hasher.update(buffered[start..i]);
454
455 const preserved = buffered[i - flate.history_len ..];
456 assert(preserved.len > @max(rebase_min_preserve, preserve));
457 @memmove(w.buffer[0..preserved.len], preserved);
458 w.end = preserved.len;
459 c.has_history = true;
460}
461
462fn addHash(c: *Compress, i: usize, h: Hash) void {
463 assert(h == hash(mem.readInt(Seq, c.writer.buffer[i..][0..seq_bytes], .big)));
464
465 const l = &c.lookup;
466 l.chain_pos +%= 1;
467
468 // Equivilent to the below, however LLVM 21 does not optimize `@subWithOverflow` well at all.
469 // const replaced_i, const no_replace = @subWithOverflow(i, flate.history_len);
470 // if (no_replace == 0) {
471 if (i >= flate.history_len) {
472 @branchHint(.likely);
473 const replaced_i = i - flate.history_len;
474 // The following is the same as the below except uses a 32-bit load to help optimizations
475 // const replaced_seq = mem.readInt(Seq, c.writer.buffer[replaced_i..][0..seq_bytes], .big);
476 comptime assert(@sizeOf(Seq) <= @sizeOf(u32));
477 const replaced_u32 = mem.readInt(u32, c.writer.buffered()[replaced_i..][0..4], .big);
478 const replaced_seq: Seq = @intCast(replaced_u32 >> (32 - @bitSizeOf(Seq)));
479
480 const replaced_h = hash(replaced_seq);
481 // The following is equivilent to the below since LLVM 21 doesn't optimize it well.
482 // l.head[replaced_h].is_null = l.head[replaced_h].is_null or
483 // l.head[replaced_h].int() == l.chain_pos;
484 const empty_head = l.head[replaced_h].int() == l.chain_pos;
485 const null_flag = PackedOptionalU15.int(.{ .is_null = empty_head, .value = 0 });
486 l.head[replaced_h] = @bitCast(l.head[replaced_h].int() | null_flag);
152487 }
153488
154 fn tokens(self: *Tokens) []const Token {
155 return self.list[0..self.pos];
489 const prev_chain_index = l.head[h];
490 l.chain[l.chain_pos] = @bitCast((l.chain_pos -% prev_chain_index.value) |
491 (prev_chain_index.int() & PackedOptionalU15.null_bit.int())); // Preserves null
492 l.head[h] = .{ .value = l.chain_pos, .is_null = false };
493}
494
495/// If the match is shorter, the returned value can be any value `<= old`.
496fn betterMatchLen(old: u16, prev: []const u8, bytes: []const u8) u16 {
497 assert(old < @min(bytes.len, token.max_length));
498 assert(prev.len >= bytes.len);
499 assert(bytes.len >= token.min_length);
500
501 var i: u16 = 0;
502 const Block = std.meta.Int(.unsigned, @min(math.divCeil(
503 comptime_int,
504 math.ceilPowerOfTwoAssert(usize, @bitSizeOf(usize)),
505 8,
506 ) catch unreachable, 256) * 8);
507
508 if (bytes.len < token.max_length) {
509 @branchHint(.unlikely); // Only end of stream
510
511 while (bytes[i..].len >= @sizeOf(Block)) {
512 const a = mem.readInt(Block, prev[i..][0..@sizeOf(Block)], .little);
513 const b = mem.readInt(Block, bytes[i..][0..@sizeOf(Block)], .little);
514 const diff = a ^ b;
515 if (diff != 0) {
516 @branchHint(.likely);
517 i += @ctz(diff) / 8;
518 return i;
519 }
520 i += @sizeOf(Block);
521 }
522
523 while (i != bytes.len and prev[i] == bytes[i]) {
524 i += 1;
525 }
526 assert(i < token.max_length);
527 return i;
156528 }
157};
158529
159fn drain(me: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
160 _ = data;
161 _ = splat;
162 const c: *Compress = @fieldParentPtr("writer", me);
163 const out = c.block_writer.output;
164 switch (c.state) {
165 .header => {
166 c.state = .middle;
167 const header = c.hasher.container().header();
168 try out.writeAll(header);
169 return header.len;
170 },
171 .middle => {},
172 .ended => unreachable,
530 if (old >= @sizeOf(Block)) {
531 // Check that a longer end is present, otherwise the match is always worse
532 const a = mem.readInt(Block, prev[old + 1 - @sizeOf(Block) ..][0..@sizeOf(Block)], .little);
533 const b = mem.readInt(Block, bytes[old + 1 - @sizeOf(Block) ..][0..@sizeOf(Block)], .little);
534 if (a != b) return i;
535 }
536
537 while (true) {
538 const a = mem.readInt(Block, prev[i..][0..@sizeOf(Block)], .little);
539 const b = mem.readInt(Block, bytes[i..][0..@sizeOf(Block)], .little);
540 const diff = a ^ b;
541 if (diff != 0) {
542 i += @ctz(diff) / 8;
543 return i;
544 }
545 i += @sizeOf(Block);
546 if (i == 256) break;
547 }
548
549 const a = mem.readInt(u16, prev[i..][0..2], .little);
550 const b = mem.readInt(u16, bytes[i..][0..2], .little);
551 const diff = a ^ b;
552 i += @ctz(diff) / 8;
553 assert(i <= token.max_length);
554 return i;
555}
556
557test betterMatchLen {
558 try std.testing.fuzz({}, testFuzzedMatchLen, .{});
559}
560
561fn testFuzzedMatchLen(_: void, input: []const u8) !void {
562 @disableInstrumentation();
563 var r: Io.Reader = .fixed(input);
564 var buf: [1024]u8 = undefined;
565 var w: Writer = .fixed(&buf);
566 var old = r.takeLeb128(u9) catch 0;
567 var bytes_off = @max(1, r.takeLeb128(u10) catch 258);
568 const prev_back = @max(1, r.takeLeb128(u10) catch 258);
569
570 while (r.takeByte()) |byte| {
571 const op: packed struct(u8) {
572 kind: enum(u2) { splat, copy, insert_imm, insert },
573 imm: u6,
574
575 pub fn immOrByte(op_s: @This(), r_s: *Io.Reader) usize {
576 return if (op_s.imm == 0) op_s.imm else @as(usize, r_s.takeByte() catch 0) + 64;
577 }
578 } = @bitCast(byte);
579 (switch (op.kind) {
580 .splat => w.splatByteAll(r.takeByte() catch 0, op.immOrByte(&r)),
581 .copy => write: {
582 const start = w.buffered().len -| op.immOrByte(&r);
583 const len = @min(w.buffered().len - start, r.takeByte() catch 3);
584 break :write w.writeAll(w.buffered()[start..][0..len]);
585 },
586 .insert_imm => w.writeByte(op.imm),
587 .insert => w.writeAll(r.take(
588 @min(r.bufferedLen(), @as(usize, op.imm) + 1),
589 ) catch unreachable),
590 }) catch break;
591 } else |_| {}
592
593 w.splatByteAll(0, (1 + 3) -| w.buffered().len) catch unreachable;
594 bytes_off = @min(bytes_off, @as(u10, @intCast(w.buffered().len - 3)));
595 const prev_off = bytes_off -| prev_back;
596 assert(prev_off < bytes_off);
597 const prev = w.buffered()[prev_off..];
598 const bytes = w.buffered()[bytes_off..];
599 old = @min(old, bytes.len - 1, token.max_length - 1);
600
601 const diff_index = mem.indexOfDiff(u8, prev, bytes).?; // unwrap since lengths are not same
602 const expected_len = @min(diff_index, 258);
603 errdefer std.debug.print(
604 \\prev : '{any}'
605 \\bytes: '{any}'
606 \\old : {}
607 \\expected: {?}
608 \\actual : {}
609 ++ "\n", .{
610 prev, bytes, old,
611 if (old < expected_len) expected_len else null, betterMatchLen(old, prev, bytes),
612 });
613 if (old < expected_len) {
614 try std.testing.expectEqual(expected_len, betterMatchLen(old, prev, bytes));
615 } else {
616 try std.testing.expect(betterMatchLen(old, prev, bytes) <= old);
617 }
618}
619
620fn matchAndAddHash(c: *Compress, i: usize, h: Hash, gt: u16, max_chain: u16, good_: u16) struct {
621 dist: u16,
622 len: u16,
623} {
624 const l = &c.lookup;
625 const buffered = c.writer.buffered();
626
627 var chain_limit = max_chain;
628 var best_dist: u16 = undefined;
629 var best_len = gt;
630 const nice = @min(c.opts.nice, buffered[i..].len);
631 var good = good_;
632
633 search: {
634 if (l.head[h].is_null) break :search;
635 // Actually a u15, but LLVM 21 does not optimize that as well (it truncates it each use).
636 var dist: u16 = l.chain_pos -% l.head[h].value;
637 while (true) {
638 chain_limit -= 1;
639
640 const match_len = betterMatchLen(best_len, buffered[i - 1 - dist ..], buffered[i..]);
641 if (match_len > best_len) {
642 best_dist = dist;
643 best_len = match_len;
644 if (best_len >= nice) break;
645 if (best_len >= good) {
646 chain_limit >>= 2;
647 good = math.maxInt(u8); // Reduce only once
648 }
649 }
650
651 if (chain_limit == 0) break;
652 const next_chain_index = l.chain_pos -% @as(u15, @intCast(dist));
653 // Equivilent to the below, however LLVM 21 optimizes the below worse.
654 // if (l.chain[next_chain_index].is_null) break;
655 // dist, const out_of_window = @addWithOverflow(dist, l.chain[next_chain_index].value);
656 // if (out_of_window == 1) break;
657 dist +%= l.chain[next_chain_index].int(); // wrapping for potential null bit
658 comptime assert(flate.history_len == PackedOptionalU15.int(.null_bit));
659 // Also, doing >= flate.history_len gives worse codegen with LLVM 21.
660 if ((dist | l.chain[next_chain_index].int()) & flate.history_len != 0) break;
661 }
662 }
663
664 c.addHash(i, h);
665 return .{ .dist = best_dist, .len = best_len };
666}
667
668fn clenHlen(freqs: [19]u16) u4 {
669 // Note that the first four codes (16, 17, 18, and 0) are always present.
670 if (builtin.mode != .ReleaseSmall and (std.simd.suggestVectorLength(u16) orelse 1) >= 8) {
671 const V = @Vector(16, u16);
672 const hlen_mul: V = comptime m: {
673 var hlen_mul: [16]u16 = undefined;
674 for (token.codegen_order[3..], 0..) |i, hlen| {
675 hlen_mul[i] = hlen;
676 }
677 break :m hlen_mul;
678 };
679 const encoded = freqs[0..16].* != @as(V, @splat(0));
680 return @intCast(@reduce(.Max, @intFromBool(encoded) * hlen_mul));
681 } else {
682 var max: u4 = 0;
683 for (token.codegen_order[4..], 1..) |i, len| {
684 max = if (freqs[i] == 0) max else @intCast(len);
685 }
686 return max;
687 }
688}
689
690test clenHlen {
691 var freqs: [19]u16 = @splat(0);
692 try std.testing.expectEqual(0, clenHlen(freqs));
693 for (token.codegen_order, 1..) |i, len| {
694 freqs[i] = 1;
695 try std.testing.expectEqual(len -| 4, clenHlen(freqs));
696 freqs[i] = 0;
697 }
698}
699
700/// Returns the number of values followed by the bitsize of the extra bits.
701fn buildClen(
702 dyn_bits: []const u4,
703 out_values: []u8,
704 out_extra: []u8,
705 out_freqs: *[19]u16,
706) struct { u16, u16 } {
707 assert(dyn_bits.len <= out_values.len);
708 assert(out_values.len == out_extra.len);
709
710 var len: u16 = 0;
711 var extra_bitsize: u16 = 0;
712
713 var remaining_bits = dyn_bits;
714 var prev: u4 = 0;
715 while (true) {
716 const b = remaining_bits[0];
717 const n_max = @min(@as(u8, if (b != 0)
718 if (b != prev) 1 else 6
719 else
720 138), remaining_bits.len);
721 prev = b;
722
723 var n: u8 = 0;
724 while (true) {
725 remaining_bits = remaining_bits[1..];
726 n += 1;
727 if (n == n_max or remaining_bits[0] != b) break;
728 }
729 const code, const extra, const xsize = switch (n) {
730 0 => unreachable,
731 1...2 => .{ b, 0, 0 },
732 3...10 => .{
733 @as(u8, 16) + @intFromBool(b == 0),
734 n - 3,
735 @as(u8, 2) + @intFromBool(b == 0),
736 },
737 11...138 => .{ 18, n - 11, 7 },
738 else => unreachable,
739 };
740 while (true) {
741 out_values[len] = code;
742 out_extra[len] = extra;
743 out_freqs[code] += 1;
744 extra_bitsize += xsize;
745 len += 1;
746 if (n != 2) {
747 @branchHint(.likely);
748 break;
749 }
750 // Code needs outputted once more
751 n = 1;
752 }
753 if (remaining_bits.len == 0) break;
754 }
755
756 return .{ len, extra_bitsize };
757}
758
759test buildClen {
760 //dyn_bits: []u4,
761 //out_values: *[288 + 30]u8,
762 //out_extra: *[288 + 30]u8,
763 //out_freqs: *[19]u16,
764 //struct { u16, u16 }
765 var out_values: [288 + 30]u8 = undefined;
766 var out_extra: [288 + 30]u8 = undefined;
767 var out_freqs: [19]u16 = @splat(0);
768 const len, const extra_bitsize = buildClen(&([_]u4{
769 1, // A
770 2, 2, // B
771 3, 3, 3, // C
772 4, 4, 4, 4, // D
773 5, // E
774 5, 5, 5, 5, 5, 5, //
775 5, 5, 5, 5, 5, 5,
776 5, 5,
777 0, 1, // F
778 0, 0, 1, // G
779 } ++ @as([138 + 10]u4, @splat(0)) // H
780 ), &out_values, &out_extra, &out_freqs);
781 try std.testing.expectEqualSlices(u8, &.{
782 1, // A
783 2, 2, // B
784 3, 3, 3, // C
785 4, 16, // D
786 5, 16, 16, 5, 5, // E
787 0, 1, // F
788 0, 0, 1, // G
789 18, 17, // H
790 }, out_values[0..len]);
791 try std.testing.expectEqualSlices(u8, &.{
792 0, // A
793 0, 0, // B
794 0, 0, 0, // C
795 0, (0), // D
796 0, (3), (3), 0, 0, // E
797 0, 0, // F
798 0, 0, 0, // G
799 (127), (7), // H
800 }, out_extra[0..len]);
801 try std.testing.expectEqual(2 + 2 + 2 + 7 + 3, extra_bitsize);
802 try std.testing.expectEqualSlices(u16, &.{
803 3, 3, 2, 3, 1, 3, 0, 0,
804 0, 0, 0, 0, 0, 0, 0, 0,
805 3, 1, 1,
806 }, &out_freqs);
807}
808
809fn writeBlock(c: *Compress, eos: bool) Writer.Error!void {
810 const toks = &c.buffered_tokens;
811 if (!eos) assert(toks.n == block_tokens);
812 assert(toks.lit_freqs[256] == 0);
813 toks.lit_freqs[256] = 1;
814
815 var dyn_codes_buf: [286 + 30]u16 = undefined;
816 var dyn_bits_buf: [286 + 30]u4 = @splat(0);
817
818 const dyn_lit_codes_bitsize, const dyn_last_lit = huffman.build(
819 &toks.lit_freqs,
820 dyn_codes_buf[0..286],
821 dyn_bits_buf[0..286],
822 15,
823 true,
824 );
825 const dyn_lit_len = @max(257, dyn_last_lit + 1);
826
827 const dyn_dist_codes_bitsize, const dyn_last_dist = huffman.build(
828 &toks.dist_freqs,
829 dyn_codes_buf[dyn_lit_len..][0..30],
830 dyn_bits_buf[dyn_lit_len..][0..30],
831 15,
832 true,
833 );
834 const dyn_dist_len = @max(1, dyn_last_dist + 1);
835
836 var clen_values: [288 + 30]u8 = undefined;
837 var clen_extra: [288 + 30]u8 = undefined;
838 var clen_freqs: [19]u16 = @splat(0);
839 const clen_len, const clen_extra_bitsize = buildClen(
840 dyn_bits_buf[0 .. dyn_lit_len + dyn_dist_len],
841 &clen_values,
842 &clen_extra,
843 &clen_freqs,
844 );
845
846 var clen_codes: [19]u16 = undefined;
847 var clen_bits: [19]u4 = @splat(0);
848 const clen_codes_bitsize, _ = huffman.build(
849 &clen_freqs,
850 &clen_codes,
851 &clen_bits,
852 7,
853 false,
854 );
855 const hclen = clenHlen(clen_freqs);
856
857 const dynamic_bitsize = @as(u32, 14) +
858 (4 + @as(u6, hclen)) * 3 + clen_codes_bitsize + clen_extra_bitsize +
859 dyn_lit_codes_bitsize + dyn_dist_codes_bitsize;
860 const fixed_bitsize = n: {
861 const freq7 = 1; // eos
862 var freq8: u16 = 0;
863 var freq9: u16 = 0;
864 var freq12: u16 = 0; // 7 + 5 - match freqs always have corresponding 5-bit dist freq
865 var freq13: u16 = 0; // 8 + 5
866 for (toks.lit_freqs[0..144]) |f| freq8 += f;
867 for (toks.lit_freqs[144..256]) |f| freq9 += f;
868 assert(toks.lit_freqs[256] == 1);
869 for (toks.lit_freqs[257..280]) |f| freq12 += f;
870 for (toks.lit_freqs[280..286]) |f| freq13 += f;
871 break :n @as(u32, freq7) * 7 +
872 @as(u32, freq8) * 8 + @as(u32, freq9) * 9 +
873 @as(u32, freq12) * 12 + @as(u32, freq13) * 13;
874 };
875
876 stored: {
877 for (toks.dist_freqs) |n| if (n != 0) break :stored;
878 // No need to check len frequencies since they each have a corresponding dist frequency
879 assert(for (toks.lit_freqs[257..]) |f| (if (f != 0) break false) else true);
880
881 // No matches. If the stored size is smaller than the huffman-encoded version, it will be
882 // outputed in a store block. This is not done with matches since the original input would
883 // need to be stored since the window may slid, and it may also exceed 65535 bytes. This
884 // should be OK since most inputs with matches should be more compressable anyways.
885 const stored_align_bits = -%(c.bit_writer.buffered_n +% 3);
886 const stored_bitsize = stored_align_bits + @as(u32, 32) + @as(u32, toks.n) * 8;
887 if (@min(dynamic_bitsize, fixed_bitsize) < stored_bitsize) break :stored;
888
889 try c.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3);
890 try c.bit_writer.output.rebase(0, 5);
891 c.bit_writer.byteAlign();
892 c.bit_writer.output.writeInt(u16, c.buffered_tokens.n, .little) catch unreachable;
893 c.bit_writer.output.writeInt(u16, ~c.buffered_tokens.n, .little) catch unreachable;
894
895 // Relatively small buffer since regular draining will
896 // always consume slightly less than 2 << 15 bytes.
897 var vec_buf: [4][]const u8 = undefined;
898 var vec_n: usize = 0;
899 var i: usize = 0;
900
901 assert(c.buffered_tokens.pos != 0);
902 while (i != c.buffered_tokens.pos) {
903 const h: TokenBufferEntryHeader = @bitCast(toks.list[i..][0..2].*);
904 assert(h.kind == .bytes);
905
906 i += 2;
907 vec_buf[vec_n] = toks.list[i..][0..h.data];
908 i += h.data;
909
910 vec_n += 1;
911 if (i == c.buffered_tokens.pos or vec_n == vec_buf.len) {
912 try c.bit_writer.output.writeVecAll(vec_buf[0..vec_n]);
913 vec_n = 0;
914 }
915 }
916
917 toks.* = .empty;
918 return;
919 }
920
921 const lit_codes, const lit_bits, const dist_codes, const dist_bits =
922 if (dynamic_bitsize < fixed_bitsize) codes: {
923 try c.bit_writer.write(BlockHeader.Dynamic.int(.{
924 .regular = .{ .final = eos, .kind = .dynamic },
925 .hlit = @intCast(dyn_lit_len - 257),
926 .hdist = @intCast(dyn_dist_len - 1),
927 .hclen = hclen,
928 }), 17);
929 try c.bit_writer.writeClen(
930 hclen,
931 clen_values[0..clen_len],
932 clen_extra[0..clen_len],
933 clen_codes,
934 clen_bits,
935 );
936 break :codes .{
937 dyn_codes_buf[0..dyn_lit_len],
938 dyn_bits_buf[0..dyn_lit_len],
939 dyn_codes_buf[dyn_lit_len..][0..dyn_dist_len],
940 dyn_bits_buf[dyn_lit_len..][0..dyn_dist_len],
941 };
942 } else codes: {
943 try c.bit_writer.write(BlockHeader.int(.{ .final = eos, .kind = .fixed }), 3);
944 break :codes .{
945 &token.fixed_lit_codes,
946 &token.fixed_lit_bits,
947 &token.fixed_dist_codes,
948 &token.fixed_dist_bits,
949 };
950 };
951
952 var i: usize = 0;
953 while (i != toks.pos) {
954 const h: TokenBufferEntryHeader = @bitCast(toks.list[i..][0..2].*);
955 i += 2;
956 if (h.kind == .bytes) {
957 for (toks.list[i..][0..h.data]) |b| {
958 try c.bit_writer.write(lit_codes[b], lit_bits[b]);
959 }
960 i += h.data;
961 } else {
962 const dist = h.data;
963 const len = toks.list[i];
964 i += 1;
965 const dist_code = token.DistCode.fromVal(dist);
966 const len_code = token.LenCode.fromVal(len);
967 const dist_val = dist_code.toInt();
968 const lit_val = @as(u16, 257) + len_code.toInt();
969
970 var out: u48 = lit_codes[lit_val];
971 var out_bits: u6 = lit_bits[lit_val];
972 out |= @shlExact(@as(u20, len - len_code.base()), @intCast(out_bits));
973 out_bits += len_code.extraBits();
974
975 out |= @shlExact(@as(u35, dist_codes[dist_val]), out_bits);
976 out_bits += dist_bits[dist_val];
977 out |= @shlExact(@as(u48, dist - dist_code.base()), out_bits);
978 out_bits += dist_code.extraBits();
979
980 try c.bit_writer.write(out, out_bits);
981 }
982 }
983 try c.bit_writer.write(lit_codes[256], lit_bits[256]);
984
985 toks.* = .empty;
986}
987
988/// Huffman tree construction.
989///
990/// The approach for building the huffman tree is [taken from zlib]
991/// (https://github.com/madler/zlib/blob/v1.3.1/trees.c#L625) with some modifications.
992const huffman = struct {
993 const max_leafs = 286;
994 const max_nodes = max_leafs * 2;
995
996 const Node = struct {
997 freq: u16,
998 depth: u16,
999
1000 pub const Index = u16;
1001
1002 pub fn smaller(a: Node, b: Node) bool {
1003 return if (a.freq != b.freq) a.freq < b.freq else a.depth < b.depth;
1004 }
1005 };
1006
1007 fn heapSiftDown(nodes: []Node, heap: []Node.Index, start: usize) void {
1008 var i = start;
1009 while (true) {
1010 var min = i;
1011 const l = i * 2 + 1;
1012 const r = l + 1;
1013 min = if (l < heap.len and nodes[heap[l]].smaller(nodes[heap[min]])) l else min;
1014 min = if (r < heap.len and nodes[heap[r]].smaller(nodes[heap[min]])) r else min;
1015 if (i == min) break;
1016 mem.swap(Node.Index, &heap[i], &heap[min]);
1017 i = min;
1018 }
1019 }
1020
1021 fn heapRemoveRoot(nodes: []Node, heap: []Node.Index) void {
1022 heap[0] = heap[heap.len - 1];
1023 heapSiftDown(nodes, heap[0 .. heap.len - 1], 0);
1024 }
1025
1026 /// Returns the total bits to encode `freqs` followed by the index of the last non-zero bits.
1027 /// For `freqs[i]` == 0, `out_codes[i]` will be undefined.
1028 /// It is asserted `out_bits` is zero-filled.
1029 /// It is asserted `out_bits.len` is at least a length of
1030 /// one if ncomplete trees are allowed and two otherwise.
1031 pub fn build(
1032 freqs: []const u16,
1033 out_codes: []u16,
1034 out_bits: []u4,
1035 max_bits: u4,
1036 incomplete_allowed: bool,
1037 ) struct { u32, u16 } {
1038 assert(out_codes.len - 1 >= @intFromBool(incomplete_allowed));
1039 // freqs and out_codes are in the loop to assert they are all the same length
1040 for (freqs, out_codes, out_bits) |_, _, n| assert(n == 0);
1041 assert(out_codes.len <= @as(u16, 1) << max_bits);
1042
1043 // Indexes 0..freqs are leafs, indexes max_leafs.. are internal nodes.
1044 var tree_nodes: [max_nodes]Node = undefined;
1045 var tree_parent_nodes: [max_nodes]Node.Index = undefined;
1046 var nodes_end: u16 = max_leafs;
1047 // Dual-purpose buffer. Nodes are ordered by least frequency or when equal, least depth.
1048 // The start is a min heap of level-zero nodes.
1049 // The end is a sorted buffer of nodes with the greatest first.
1050 var node_buf: [max_nodes]Node.Index = undefined;
1051 var heap_end: u16 = 0;
1052 var sorted_start: u16 = node_buf.len;
1053
1054 for (0.., freqs) |n, freq| {
1055 tree_nodes[n] = .{ .freq = freq, .depth = 0 };
1056 node_buf[heap_end] = @intCast(n);
1057 heap_end += @intFromBool(freq != 0);
1058 }
1059
1060 // There must be at least one code at minimum,
1061 node_buf[heap_end] = 0;
1062 heap_end += @intFromBool(heap_end == 0);
1063 // and at least two if incomplete must be avoided.
1064 if (heap_end == 1 and incomplete_allowed) {
1065 @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without
1066
1067 // Codes must have at least one-bit, so this is a special case.
1068 out_bits[node_buf[0]] = 1;
1069 out_codes[node_buf[0]] = 0;
1070 return .{ freqs[node_buf[0]], node_buf[0] };
1071 }
1072 const last_nonzero = @max(node_buf[heap_end - 1], 1); // For heap_end > 1, last is not be 0
1073 node_buf[heap_end] = @intFromBool(node_buf[0] == 0);
1074 heap_end += @intFromBool(heap_end == 1);
1075
1076 // Heapify the array of frequencies
1077 const heapify_final = heap_end - 1;
1078 const heapify_start = (heapify_final - 1) / 2; // Parent of final node
1079 var heapify_i = heapify_start;
1080 while (true) {
1081 heapSiftDown(&tree_nodes, node_buf[0..heap_end], heapify_i);
1082 if (heapify_i == 0) break;
1083 heapify_i -= 1;
1084 }
1085
1086 // Build optimal tree. `max_bits` is not enforced yet.
1087 while (heap_end > 1) {
1088 const a = node_buf[0];
1089 heapRemoveRoot(&tree_nodes, node_buf[0..heap_end]);
1090 heap_end -= 1;
1091 const b = node_buf[0];
1092
1093 sorted_start -= 2;
1094 node_buf[sorted_start..][0..2].* = .{ b, a };
1095
1096 tree_nodes[nodes_end] = .{
1097 .freq = tree_nodes[a].freq + tree_nodes[b].freq,
1098 .depth = @max(tree_nodes[a].depth, tree_nodes[b].depth) + 1,
1099 };
1100 defer nodes_end += 1;
1101 tree_parent_nodes[a] = nodes_end;
1102 tree_parent_nodes[b] = nodes_end;
1103
1104 node_buf[0] = nodes_end;
1105 heapSiftDown(&tree_nodes, node_buf[0..heap_end], 0);
1106 }
1107 sorted_start -= 1;
1108 node_buf[sorted_start] = node_buf[0];
1109
1110 var bit_counts: [16]u16 = @splat(0);
1111 buildBits(out_bits, &bit_counts, &tree_parent_nodes, node_buf[sorted_start..], max_bits);
1112 return .{ buildValues(freqs, out_codes, out_bits, bit_counts), last_nonzero };
1113 }
1114
1115 fn buildBits(
1116 out_bits: []u4,
1117 bit_counts: *[16]u16,
1118 parent_nodes: *[max_nodes]Node.Index,
1119 sorted: []Node.Index,
1120 max_bits: u4,
1121 ) void {
1122 var internal_node_bits: [max_nodes - max_leafs]u4 = undefined;
1123 var overflowed: u16 = 0;
1124
1125 internal_node_bits[sorted[0] - max_leafs] = 0; // root
1126 for (sorted[1..]) |i| {
1127 const parent_bits = internal_node_bits[parent_nodes[i] - max_leafs];
1128 overflowed += @intFromBool(parent_bits == max_bits);
1129 const bits = parent_bits + @intFromBool(parent_bits != max_bits);
1130 bit_counts[bits] += @intFromBool(i < max_leafs);
1131 (if (i >= max_leafs) &internal_node_bits[i - max_leafs] else &out_bits[i]).* = bits;
1132 }
1133
1134 if (overflowed == 0) {
1135 @branchHint(.likely);
1136 return;
1137 }
1138
1139 outer: while (true) {
1140 var deepest: u4 = max_bits - 1;
1141 while (bit_counts[deepest] == 0) deepest -= 1;
1142 while (overflowed != 0) {
1143 // Insert an internal node under the leaf and move an overflow as its sibling
1144 bit_counts[deepest] -= 1;
1145 bit_counts[deepest + 1] += 2;
1146 // Only overflow moved. Its sibling's depth is one less, however is still >= depth.
1147 bit_counts[max_bits] -= 1;
1148 overflowed -= 2;
1149
1150 if (overflowed == 0) break :outer;
1151 deepest += 1;
1152 if (deepest == max_bits) continue :outer;
1153 }
1154 }
1155
1156 // Reassign bit lengths
1157 assert(bit_counts[0] == 0);
1158 var i: usize = 0;
1159 for (1.., bit_counts[1..]) |bits, all| {
1160 var remaining = all;
1161 while (remaining != 0) {
1162 defer i += 1;
1163 if (sorted[i] >= max_leafs) continue;
1164 out_bits[sorted[i]] = @intCast(bits);
1165 remaining -= 1;
1166 }
1167 }
1168 assert(for (sorted[i..]) |n| { // all leafs consumed
1169 if (n < max_leafs) break false;
1170 } else true);
1171 }
1172
1173 fn buildValues(freqs: []const u16, out_codes: []u16, bits: []u4, bit_counts: [16]u16) u32 {
1174 var code: u16 = 0;
1175 var base: [16]u16 = undefined;
1176 assert(bit_counts[0] == 0);
1177 for (bit_counts[1..], base[1..]) |c, *b| {
1178 b.* = code;
1179 code +%= c;
1180 code <<= 1;
1181 }
1182 var freq_sums: [16]u16 = @splat(0);
1183 for (out_codes, bits, freqs) |*c, b, f| {
1184 c.* = @bitReverse(base[b]) >> -%b;
1185 base[b] += 1; // For `b == 0` this is fine since v is specified to be undefined.
1186 freq_sums[b] += f;
1187 }
1188 return @reduce(.Add, @as(@Vector(16, u32), freq_sums) * std.simd.iota(u32, 16));
1189 }
1190
1191 test build {
1192 var codes: [8]u16 = undefined;
1193 var bits: [8]u4 = undefined;
1194
1195 const regular_freqs: [8]u16 = .{ 1, 1, 0, 8, 8, 0, 2, 4 };
1196 // The optimal tree for the above frequencies is
1197 // 4 1 1
1198 // \ /
1199 // 3 2 #
1200 // \ /
1201 // 2 8 8 4 #
1202 // \ / \ /
1203 // 1 # #
1204 // \ /
1205 // 0 #
1206 bits = @splat(0);
1207 var n, var lnz = build(&regular_freqs, &codes, &bits, 15, true);
1208 codes[2] = 0;
1209 codes[5] = 0;
1210 try std.testing.expectEqualSlices(u4, &.{ 4, 4, 0, 2, 2, 0, 3, 2 }, &bits);
1211 try std.testing.expectEqualSlices(u16, &.{
1212 0b0111, 0b1111, 0, 0b00, 0b10, 0, 0b011, 0b01,
1213 }, &codes);
1214 try std.testing.expectEqual(54, n);
1215 try std.testing.expectEqual(7, lnz);
1216 // When constrained to 3 bits, it becomes
1217 // 3 1 1 2 4
1218 // \ / \ /
1219 // 2 8 8 # #
1220 // \ / \ /
1221 // 1 # #
1222 // \ /
1223 // 0 #
1224 bits = @splat(0);
1225 n, lnz = build(&regular_freqs, &codes, &bits, 3, true);
1226 codes[2] = 0;
1227 codes[5] = 0;
1228 try std.testing.expectEqualSlices(u4, &.{ 3, 3, 0, 2, 2, 0, 3, 3 }, &bits);
1229 try std.testing.expectEqualSlices(u16, &.{
1230 0b001, 0b101, 0, 0b00, 0b10, 0, 0b011, 0b111,
1231 }, &codes);
1232 try std.testing.expectEqual(56, n);
1233 try std.testing.expectEqual(7, lnz);
1234
1235 // Empty tree. At least one code should be present
1236 bits = @splat(0);
1237 n, lnz = build(&.{ 0, 0 }, codes[0..2], bits[0..2], 15, true);
1238 try std.testing.expectEqualSlices(u4, &.{ 1, 0 }, bits[0..2]);
1239 try std.testing.expectEqual(0b0, codes[0]);
1240 try std.testing.expectEqual(0, n);
1241 try std.testing.expectEqual(0, lnz);
1242
1243 // Check all incompletable frequencies are completed
1244 for ([_][2]u16{ .{ 0, 0 }, .{ 0, 1 }, .{ 1, 0 } }) |incomplete| {
1245 // Empty tree. Both codes should be present to prevent incomplete trees
1246 bits = @splat(0);
1247 n, lnz = build(&incomplete, codes[0..2], bits[0..2], 15, false);
1248 try std.testing.expectEqualSlices(u4, &.{ 1, 1 }, bits[0..2]);
1249 try std.testing.expectEqualSlices(u16, &.{ 0b0, 0b1 }, codes[0..2]);
1250 try std.testing.expectEqual(incomplete[0] + incomplete[1], n);
1251 try std.testing.expectEqual(1, lnz);
1252 }
1253
1254 try std.testing.fuzz({}, checkFuzzedBuildFreqs, .{});
1731255 }
1741256
175 const buffered = me.buffered();
176 const min_lookahead = Token.min_length + Token.max_length;
177 const history_plus_lookahead_len = flate.history_len + min_lookahead;
178 if (buffered.len < history_plus_lookahead_len) return 0;
179 const lookahead = buffered[flate.history_len..];
1257 fn checkFuzzedBuildFreqs(_: void, freqs: []const u8) !void {
1258 @disableInstrumentation();
1259 var r: Io.Reader = .fixed(freqs);
1260 var freqs_limit: u16 = 65535;
1261 var freqs_buf: [max_leafs]u16 = undefined;
1262 var nfreqs: u15 = 0;
1263
1264 const params: packed struct(u8) {
1265 max_bits: u4,
1266 _: u3,
1267 incomplete_allowed: bool,
1268 } = @bitCast(r.takeByte() catch 255);
1269 while (nfreqs != freqs_buf.len) {
1270 const leb = r.takeLeb128(u16);
1271 const f = if (leb) |f| @min(f, freqs_limit) else |e| switch (e) {
1272 error.ReadFailed => unreachable,
1273 error.EndOfStream => 0,
1274 error.Overflow => freqs_limit,
1275 };
1276 freqs_buf[nfreqs] = f;
1277 nfreqs += 1;
1278 freqs_limit -= f;
1279 if (leb == error.EndOfStream and nfreqs - 1 > @intFromBool(params.incomplete_allowed))
1280 break;
1281 }
1282
1283 var codes_buf: [max_leafs]u16 = undefined;
1284 var bits_buf: [max_leafs]u4 = @splat(0);
1285 const total_bits, const last_nonzero = build(
1286 freqs_buf[0..nfreqs],
1287 codes_buf[0..nfreqs],
1288 bits_buf[0..nfreqs],
1289 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),
1290 params.incomplete_allowed,
1291 );
1292
1293 var has_bitlen_one: bool = false;
1294 var expected_total_bits: u32 = 0;
1295 var expected_last_nonzero: ?u16 = null;
1296 var weighted_sum: u32 = 0;
1297 for (freqs_buf[0..nfreqs], bits_buf[0..nfreqs], 0..) |f, nb, i| {
1298 has_bitlen_one = has_bitlen_one or nb == 1;
1299 weighted_sum += @shlExact(@as(u16, 1), 15 - nb) & ((1 << 15) - 1);
1300 expected_total_bits += @as(u32, f) * nb;
1301 if (nb != 0) expected_last_nonzero = @intCast(i);
1302 }
1303
1304 errdefer std.log.err(
1305 \\ params: {}
1306 \\ freqs: {any}
1307 \\ bits: {any}
1308 \\ # freqs: {}
1309 \\ max bits: {}
1310 \\ weighted sum: {}
1311 \\ has_bitlen_one: {}
1312 \\ expected/actual total bits: {}/{}
1313 \\ expected/actual last nonzero: {?}/{}
1314 ++ "\n", .{
1315 params,
1316 freqs_buf[0..nfreqs],
1317 bits_buf[0..nfreqs],
1318 nfreqs,
1319 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),
1320 weighted_sum,
1321 has_bitlen_one,
1322 expected_total_bits,
1323 total_bits,
1324 expected_last_nonzero,
1325 last_nonzero,
1326 });
1327
1328 try std.testing.expectEqual(expected_total_bits, total_bits);
1329 try std.testing.expectEqual(expected_last_nonzero, last_nonzero);
1330 if (weighted_sum > 1 << 15)
1331 return error.OversubscribedHuffmanTree;
1332 if (weighted_sum < 1 << 15 and
1333 !(params.incomplete_allowed and has_bitlen_one and weighted_sum == 1 << 14))
1334 return error.IncompleteHuffmanTree;
1335 }
1336};
1801337
181 // TODO tokenize
182 _ = lookahead;
183 //c.hasher.update(lookahead[0..n]);
184 @panic("TODO");
1338test {
1339 _ = huffman;
1851340}
1861341
187pub fn end(c: *Compress) !void {
188 try endUnflushed(c);
189 const out = c.block_writer.output;
190 try out.flush();
1342/// [0] is a gradient where the probability of lower values decreases across it
1343/// [1] is completely random and hence uncompressable
1344fn testingFreqBufs() !*[2][65536]u8 {
1345 const fbufs = try std.testing.allocator.create([2][65536]u8);
1346 var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
1347 prng.random().bytes(&fbufs[0]);
1348 prng.random().bytes(&fbufs[1]);
1349 for (0.., &fbufs[0], fbufs[1]) |i, *grad, rand| {
1350 const prob = @as(u8, @intCast(255 - i / (fbufs[0].len * 256)));
1351 grad.* /= @max(1, rand / @max(1, prob));
1352 }
1353 return fbufs;
1911354}
1921355
193pub fn endUnflushed(c: *Compress) !void {
194 while (c.writer.end != 0) _ = try drain(&c.writer, &.{""}, 1);
195 c.state = .ended;
1356fn testingCheckDecompressedMatches(
1357 flate_bytes: []const u8,
1358 expected_size: u32,
1359 expected_hash: flate.Container.Hasher,
1360) !void {
1361 const container: flate.Container = expected_hash;
1362 var data_hash: flate.Container.Hasher = .init(container);
1363 var data_size: u32 = 0;
1364 var flate_r: Io.Reader = .fixed(flate_bytes);
1365 var deflate_buf: [flate.max_window_len]u8 = undefined;
1366 var deflate: flate.Decompress = .init(&flate_r, container, &deflate_buf);
1961367
197 const out = c.block_writer.output;
1368 while (deflate.reader.peekGreedy(1)) |bytes| {
1369 data_size += @intCast(bytes.len);
1370 data_hash.update(bytes);
1371 deflate.reader.toss(bytes.len);
1372 } else |e| switch (e) {
1373 error.ReadFailed => return deflate.err.?,
1374 error.EndOfStream => {},
1375 }
1981376
199 // TODO flush tokens
1377 try testingCheckContainerHash(
1378 expected_size,
1379 expected_hash,
1380 data_hash,
1381 data_size,
1382 deflate.container_metadata,
1383 );
1384}
2001385
201 switch (c.hasher) {
202 .gzip => |*gzip| {
203 // GZIP 8 bytes footer
204 // - 4 bytes, CRC32 (CRC-32)
205 // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32
206 const footer = try out.writableArray(8);
207 std.mem.writeInt(u32, footer[0..4], gzip.crc.final(), .little);
208 std.mem.writeInt(u32, footer[4..8], @truncate(gzip.count), .little);
1386fn testingCheckContainerHash(
1387 expected_size: u32,
1388 expected_hash: flate.Container.Hasher,
1389 actual_hash: flate.Container.Hasher,
1390 actual_size: u32,
1391 actual_meta: flate.Container.Metadata,
1392) !void {
1393 try std.testing.expectEqual(expected_size, actual_size);
1394 switch (actual_hash) {
1395 .raw => {},
1396 .gzip => |gz| {
1397 const expected_crc = expected_hash.gzip.crc.final();
1398 try std.testing.expectEqual(expected_size, actual_meta.gzip.count);
1399 try std.testing.expectEqual(expected_crc, gz.crc.final());
1400 try std.testing.expectEqual(expected_crc, actual_meta.gzip.crc);
2091401 },
210 .zlib => |*zlib| {
211 // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
212 // 4 bytes of ADLER32 (Adler-32 checksum)
213 // Checksum value of the uncompressed data (excluding any
214 // dictionary data) computed according to Adler-32
215 // algorithm.
216 std.mem.writeInt(u32, try out.writableArray(4), zlib.adler, .big);
1402 .zlib => |zl| {
1403 const expected_adler = expected_hash.zlib.adler;
1404 try std.testing.expectEqual(expected_adler, zl.adler);
1405 try std.testing.expectEqual(expected_adler, actual_meta.zlib.adler);
2171406 },
218 .raw => {},
2191407 }
2201408}
2211409
222pub const Simple = struct {
223 /// Note that store blocks are limited to 65535 bytes.
224 buffer: []u8,
225 wp: usize,
226 block_writer: BlockWriter,
227 hasher: Container.Hasher,
228 strategy: Strategy,
1410const PackedContainer = packed struct(u2) {
1411 raw: bool,
1412 other: enum(u1) { gzip, zlib },
1413
1414 pub fn val(c: @This()) flate.Container {
1415 return if (c.raw) .raw else switch (c.other) {
1416 .gzip => .gzip,
1417 .zlib => .zlib,
1418 };
1419 }
1420};
1421
1422test Compress {
1423 const fbufs = try testingFreqBufs();
1424 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);
1425 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});
1426}
1427
1428fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void {
1429 var in: Io.Reader = .fixed(input);
1430 var opts: packed struct(u51) {
1431 container: PackedContainer,
1432 buf_size: u16,
1433 good: u8,
1434 nice: u8,
1435 lazy: u8,
1436 /// Not a `u16` to limit it for performance
1437 chain: u9,
1438 } = @bitCast(in.takeLeb128(u51) catch 0);
1439 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
1440 var expected_size: u32 = 0;
1441
1442 var flate_buf: [128 * 1024]u8 = undefined;
1443 var flate_w: Writer = .fixed(&flate_buf);
1444 var deflate_buf: [flate.max_window_len * 2]u8 = undefined;
1445 var deflate_w = try Compress.init(
1446 &flate_w,
1447 deflate_buf[0 .. flate.max_window_len + @as(usize, opts.buf_size)],
1448 opts.container.val(),
1449 .{
1450 .good = @as(u16, opts.good) + 3,
1451 .nice = @as(u16, opts.nice) + 3,
1452 .lazy = @as(u16, @min(opts.lazy, opts.nice)) + 3,
1453 .chain = @max(1, opts.chain, @as(u8, 4) * @intFromBool(opts.good <= opts.lazy)),
1454 },
1455 );
1456
1457 // It is ensured that more bytes are not written then this to ensure this run
1458 // does not take too long and that `flate_buf` does not run out of space.
1459 const flate_buf_blocks = flate_buf.len / block_tokens;
1460 // Allow a max overhead of 64 bytes per block since the implementation does not gaurauntee it
1461 // writes store blocks when optimal. This comes from taking less than 32 bytes to write an
1462 // optimal dynamic block header of mostly bitlen 8 codes and the end of block literal plus
1463 // `(65536 / 256) / 8`, which is is the maximum number of extra bytes from bitlen 9 codes. An
1464 // extra 32 bytes is reserved on top of that for container headers and footers.
1465 const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32);
1466
1467 while (true) {
1468 const data: packed struct(u36) {
1469 is_rebase: bool,
1470 is_bytes: bool,
1471 params: packed union {
1472 copy: packed struct(u34) {
1473 len_lo: u5,
1474 dist: u15,
1475 len_hi: u4,
1476 _: u10,
1477 },
1478 bytes: packed struct(u34) {
1479 kind: enum(u1) { gradient, random },
1480 off_hi: u4,
1481 len_lo: u10,
1482 off_mi: u4,
1483 len_hi: u5,
1484 off_lo: u8,
1485 _: u2,
1486 },
1487 rebase: packed struct(u34) {
1488 preserve: u17,
1489 capacity: u17,
1490 },
1491 },
1492 } = @bitCast(in.takeLeb128(u36) catch |e| switch (e) {
1493 error.ReadFailed => unreachable,
1494 error.Overflow => 0,
1495 error.EndOfStream => break,
1496 });
1497
1498 const buffered = deflate_w.writer.buffered();
1499 // Required for repeating patterns and since writing from `buffered` is illegal
1500 var copy_buf: [512]u8 = undefined;
1501
1502 if (data.is_rebase) {
1503 const usable_capacity = deflate_w.writer.buffer.len - rebase_reserved_capacity;
1504 const preserve = @min(data.params.rebase.preserve, usable_capacity);
1505 const capacity = @min(data.params.rebase.capacity, usable_capacity -
1506 @max(rebase_min_preserve, preserve));
1507 try deflate_w.writer.rebase(preserve, capacity);
1508 continue;
1509 }
1510
1511 const max_bytes = max_size -| expected_size;
1512 const bytes = if (!data.is_bytes and buffered.len != 0) bytes: {
1513 const dist = @min(buffered.len, @as(u32, data.params.copy.dist) + 1);
1514 const len = @min(
1515 @max(@shlExact(@as(u9, data.params.copy.len_hi), 5) | data.params.copy.len_lo, 1),
1516 max_bytes,
1517 );
1518 // Reuse the implementation's history. Otherwise our own would need maintained.
1519 const bytes_start = buffered[buffered.len - dist ..];
1520 const history_bytes = bytes_start[0..@min(bytes_start.len, len)];
1521
1522 @memcpy(copy_buf[0..history_bytes.len], history_bytes);
1523 const new_history = len - history_bytes.len;
1524 if (history_bytes.len != len) for ( // check needed for `- dist`
1525 copy_buf[history_bytes.len..][0..new_history],
1526 copy_buf[history_bytes.len - dist ..][0..new_history],
1527 ) |*next, prev| {
1528 next.* = prev;
1529 };
1530 break :bytes copy_buf[0..len];
1531 } else bytes: {
1532 const off = @shlExact(@as(u16, data.params.bytes.off_hi), 12) |
1533 @shlExact(@as(u16, data.params.bytes.off_mi), 8) |
1534 data.params.bytes.off_lo;
1535 const len = @shlExact(@as(u16, data.params.bytes.len_hi), 10) |
1536 data.params.bytes.len_lo;
1537 const fbuf = &fbufs[@intFromEnum(data.params.bytes.kind)];
1538 break :bytes fbuf[off..][0..@min(len, fbuf.len - off, max_bytes)];
1539 };
1540 assert(bytes.len <= max_bytes);
1541 try deflate_w.writer.writeAll(bytes);
1542 expected_hash.update(bytes);
1543 expected_size += @intCast(bytes.len);
1544 }
1545
1546 try deflate_w.writer.flush();
1547 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
1548}
1549
1550/// Does not compress data
1551pub const Raw = struct {
1552 /// After `flush` is called, all vtable calls with result in `error.WriteFailed.`
1553 writer: Writer,
1554 output: *Writer,
1555 hasher: flate.Container.Hasher,
2291556
230 pub const Strategy = enum { huffman, store };
1557 const max_block_size: u16 = 65535;
1558 const full_header: [5]u8 = .{
1559 BlockHeader.int(.{ .final = false, .kind = .stored }),
1560 255,
1561 255,
1562 0,
1563 0,
1564 };
2311565
232 pub fn init(output: *Writer, buffer: []u8, container: Container, strategy: Strategy) !Simple {
233 const header = container.header();
234 try output.writeAll(header);
1566 /// While there is no minimum buffer size, it is recommended
1567 /// to be at least `flate.max_window_len` for optimal output.
1568 pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Raw {
1569 try output.writeAll(container.header());
2351570 return .{
236 .buffer = buffer,
237 .wp = 0,
238 .block_writer = .init(output),
1571 .writer = .{
1572 .buffer = buffer,
1573 .vtable = &.{
1574 .drain = Raw.drain,
1575 .flush = Raw.flush,
1576 .rebase = Raw.rebase,
1577 },
1578 },
1579 .output = output,
2391580 .hasher = .init(container),
240 .strategy = strategy,
2411581 };
2421582 }
2431583
244 pub fn flush(self: *Simple) !void {
245 try self.flushBuffer(false);
246 try self.block_writer.storedBlock("", false);
247 try self.block_writer.flush();
1584 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
1585 errdefer w.* = .failing;
1586 const r: *Raw = @fieldParentPtr("writer", w);
1587 const min_block = @min(w.buffer.len, max_block_size);
1588 const pattern = data[data.len - 1];
1589 var partial_header: [5]u8 = undefined;
1590
1591 var vecs: [16][]const u8 = undefined;
1592 var vecs_n: usize = 0;
1593 const data_bytes = Writer.countSplat(data, splat);
1594 const total_bytes = w.end + data_bytes;
1595 var rem_bytes = total_bytes;
1596 var rem_splat = splat;
1597 var rem_data = data;
1598 var rem_data_elem: []const u8 = w.buffered();
1599
1600 assert(rem_bytes > min_block);
1601 while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final
1602 // also, it handles the case of `min_block` being zero (no buffer)
1603 const block_size: u16 = @min(rem_bytes, max_block_size);
1604 rem_bytes -= block_size;
1605
1606 if (vecs_n == vecs.len) {
1607 try r.output.writeVecAll(&vecs);
1608 vecs_n = 0;
1609 }
1610 vecs[vecs_n] = if (block_size == 65535)
1611 &full_header
1612 else header: {
1613 partial_header[0] = BlockHeader.int(.{ .final = false, .kind = .stored });
1614 mem.writeInt(u16, partial_header[1..3], block_size, .little);
1615 mem.writeInt(u16, partial_header[3..5], ~block_size, .little);
1616 break :header &partial_header;
1617 };
1618 vecs_n += 1;
1619
1620 var block_limit: Io.Limit = .limited(block_size);
1621 while (true) {
1622 if (vecs_n == vecs.len) {
1623 try r.output.writeVecAll(&vecs);
1624 vecs_n = 0;
1625 }
1626
1627 const vec = block_limit.sliceConst(rem_data_elem);
1628 vecs[vecs_n] = vec;
1629 vecs_n += 1;
1630 r.hasher.update(vec);
1631
1632 const is_pattern = rem_splat != splat and vec.len == pattern.len;
1633 if (is_pattern) assert(pattern.len != 0); // exceeded countSplat
1634
1635 if (!is_pattern or rem_splat == 0 or pattern.len > @intFromEnum(block_limit) / 2) {
1636 rem_data_elem = rem_data_elem[vec.len..];
1637 block_limit = block_limit.subtract(vec.len).?;
1638
1639 if (rem_data_elem.len == 0) {
1640 rem_data_elem = rem_data[0];
1641 if (rem_data.len != 1) {
1642 rem_data = rem_data[1..];
1643 } else if (rem_splat != 0) {
1644 rem_splat -= 1;
1645 } else {
1646 // All of `data` has been consumed.
1647 assert(block_limit == .nothing);
1648 assert(rem_bytes == 0);
1649 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
1650 rem_data = undefined;
1651 rem_data_elem = undefined;
1652 rem_splat = undefined;
1653 }
1654 }
1655 if (block_limit == .nothing) break;
1656 } else {
1657 const out_splat = @intFromEnum(block_limit) / pattern.len;
1658 assert(out_splat >= 2);
1659
1660 try r.output.writeSplatAll(vecs[0..vecs_n], out_splat);
1661 for (1..out_splat) |_| r.hasher.update(vec);
1662
1663 vecs_n = 0;
1664 block_limit = block_limit.subtract(pattern.len * out_splat).?;
1665 if (rem_splat >= out_splat) {
1666 // `out_splat` contains `rem_data`, however one more needs subtracted
1667 // anyways since the next pattern is also being taken.
1668 rem_splat -= out_splat;
1669 } else {
1670 // All of `data` has been consumed.
1671 assert(block_limit == .nothing);
1672 assert(rem_bytes == 0);
1673 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
1674 rem_data = undefined;
1675 rem_data_elem = undefined;
1676 rem_splat = undefined;
1677 }
1678 if (block_limit == .nothing) break;
1679 }
1680 }
1681 }
1682
1683 if (vecs_n != 0) { // can be the case if a splat was sent
1684 try r.output.writeVecAll(vecs[0..vecs_n]);
1685 }
1686
1687 if (rem_bytes > data_bytes) {
1688 assert(rem_bytes - data_bytes == rem_data_elem.len);
1689 assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]);
1690 }
1691 return w.consume(total_bytes - rem_bytes);
1692 }
1693
1694 fn flush(w: *Writer) Writer.Error!void {
1695 defer w.* = .failing;
1696 try Raw.rebaseInner(w, 0, w.buffer.len, true);
2481697 }
2491698
250 pub fn finish(self: *Simple) !void {
251 try self.flushBuffer(true);
252 try self.block_writer.flush();
253 try self.hasher.container().writeFooter(&self.hasher, self.block_writer.output);
1699 fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
1700 errdefer w.* = .failing;
1701 try Raw.rebaseInner(w, preserve, capacity, false);
2541702 }
2551703
256 fn flushBuffer(self: *Simple, final: bool) !void {
257 const buf = self.buffer[0..self.wp];
258 switch (self.strategy) {
259 .huffman => try self.block_writer.huffmanBlock(buf, final),
260 .store => try self.block_writer.storedBlock(buf, final),
1704 fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
1705 const r: *Raw = @fieldParentPtr("writer", w);
1706 assert(preserve + capacity <= w.buffer.len);
1707 if (eos) assert(capacity == w.buffer.len);
1708
1709 var partial_header: [5]u8 = undefined;
1710 var footer_buf: [8]u8 = undefined;
1711 const preserved = @min(w.end, preserve);
1712 var remaining = w.buffer[0 .. w.end - preserved];
1713
1714 var vecs: [16][]const u8 = undefined;
1715 var vecs_n: usize = 0;
1716 while (remaining.len > max_block_size) { // not >= so there is always a block down below
1717 if (vecs_n == vecs.len) {
1718 try r.output.writeVecAll(&vecs);
1719 vecs_n = 0;
1720 }
1721 vecs[vecs_n + 0] = &full_header;
1722 vecs[vecs_n + 1] = remaining[0..max_block_size];
1723 r.hasher.update(vecs[vecs_n + 1]);
1724 vecs_n += 2;
1725 remaining = remaining[max_block_size..];
1726 }
1727
1728 // eos check required for empty block
1729 if (w.buffer.len - (remaining.len + preserved) < capacity or eos) {
1730 // A partial write is necessary to reclaim enough buffer space
1731 const block_size: u16 = @intCast(remaining.len);
1732 partial_header[0] = BlockHeader.int(.{ .final = eos, .kind = .stored });
1733 mem.writeInt(u16, partial_header[1..3], block_size, .little);
1734 mem.writeInt(u16, partial_header[3..5], ~block_size, .little);
1735
1736 if (vecs_n == vecs.len) {
1737 try r.output.writeVecAll(&vecs);
1738 vecs_n = 0;
1739 }
1740 vecs[vecs_n + 0] = &partial_header;
1741 vecs[vecs_n + 1] = remaining[0..block_size];
1742 r.hasher.update(vecs[vecs_n + 1]);
1743 vecs_n += 2;
1744 remaining = remaining[block_size..];
1745 assert(remaining.len == 0);
1746
1747 if (eos and r.hasher != .raw) {
1748 // the footer is done here instead of `flush` so it can be included in the vector
1749 var footer_w: Writer = .fixed(&footer_buf);
1750 r.hasher.writeFooter(&footer_w) catch unreachable;
1751 assert(footer_w.end != 0);
1752
1753 if (vecs_n == vecs.len) {
1754 try r.output.writeVecAll(&vecs);
1755 return r.output.writeAll(footer_w.buffered());
1756 } else {
1757 vecs[vecs_n] = footer_w.buffered();
1758 vecs_n += 1;
1759 }
1760 }
2611761 }
262 self.wp = 0;
1762
1763 try r.output.writeVecAll(vecs[0..vecs_n]);
1764 _ = w.consume(w.end - preserved - remaining.len);
2631765 }
2641766};
2651767
266test "generate a Huffman code from an array of frequencies" {
267 var freqs: [19]u16 = [_]u16{
268 8, // 0
269 1, // 1
270 1, // 2
271 2, // 3
272 5, // 4
273 10, // 5
274 9, // 6
275 1, // 7
276 0, // 8
277 0, // 9
278 0, // 10
279 0, // 11
280 0, // 12
281 0, // 13
282 0, // 14
283 0, // 15
284 1, // 16
285 3, // 17
286 5, // 18
1768test Raw {
1769 const data_buf = try std.testing.allocator.create([4 * 65536]u8);
1770 defer if (!builtin.fuzz) std.testing.allocator.destroy(data_buf);
1771 var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
1772 prng.random().bytes(data_buf);
1773 try std.testing.fuzz(data_buf, testFuzzedRawInput, .{});
1774}
1775
1776fn countVec(data: []const []const u8) usize {
1777 var bytes: usize = 0;
1778 for (data) |d| bytes += d.len;
1779 return bytes;
1780}
1781
1782fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
1783 const HashedStoreWriter = struct {
1784 writer: Writer,
1785 state: enum {
1786 header,
1787 block_header,
1788 block_body,
1789 final_block_body,
1790 footer,
1791 end,
1792 },
1793 block_remaining: u16,
1794 container: flate.Container,
1795 data_hash: flate.Container.Hasher,
1796 data_size: usize,
1797 footer_hash: u32,
1798 footer_size: u32,
1799
1800 pub fn init(buf: []u8, container: flate.Container) @This() {
1801 return .{
1802 .writer = .{
1803 .vtable = &.{
1804 .drain = @This().drain,
1805 .flush = @This().flush,
1806 },
1807 .buffer = buf,
1808 },
1809 .state = .header,
1810 .block_remaining = 0,
1811 .container = container,
1812 .data_hash = .init(container),
1813 .data_size = 0,
1814 .footer_hash = undefined,
1815 .footer_size = undefined,
1816 };
1817 }
1818
1819 /// Note that this implementation is somewhat dependent on the implementation of
1820 /// `Raw` by expecting headers / footers to be continous in data elements. It
1821 /// also expects the header to be the same as `flate.Container.header` and not
1822 /// for multiple streams to be concatenated.
1823 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
1824 errdefer w.* = .failing;
1825 var h: *@This() = @fieldParentPtr("writer", w);
1826
1827 var rem_splat = splat;
1828 var rem_data = data;
1829 var rem_data_elem: []const u8 = w.buffered();
1830
1831 data_loop: while (true) {
1832 const wanted = switch (h.state) {
1833 .header => h.container.headerSize(),
1834 .block_header => 5,
1835 .block_body, .final_block_body => h.block_remaining,
1836 .footer => h.container.footerSize(),
1837 .end => 1,
1838 };
1839
1840 if (wanted != 0) {
1841 while (rem_data_elem.len == 0) {
1842 rem_data_elem = rem_data[0];
1843 if (rem_data.len != 1) {
1844 rem_data = rem_data[1..];
1845 } else {
1846 if (rem_splat == 0) {
1847 break :data_loop;
1848 } else {
1849 rem_splat -= 1;
1850 }
1851 }
1852 }
1853 }
1854
1855 const bytes = Io.Limit.limited(wanted).sliceConst(rem_data_elem);
1856 rem_data_elem = rem_data_elem[bytes.len..];
1857
1858 switch (h.state) {
1859 .header => {
1860 if (bytes.len < wanted)
1861 return error.WriteFailed; // header eos
1862 if (!mem.eql(u8, bytes, h.container.header()))
1863 return error.WriteFailed; // wrong header
1864 h.state = .block_header;
1865 },
1866 .block_header => {
1867 if (bytes.len < wanted)
1868 return error.WriteFailed; // store block header eos
1869 const header: BlockHeader = @bitCast(@as(u3, @truncate(bytes[0])));
1870 if (header.kind != .stored)
1871 return error.WriteFailed; // non-store block
1872 const len = mem.readInt(u16, bytes[1..3], .little);
1873 const nlen = mem.readInt(u16, bytes[3..5], .little);
1874 if (nlen != ~len)
1875 return error.WriteFailed; // wrong nlen
1876 h.block_remaining = len;
1877 h.state = if (!header.final) .block_body else .final_block_body;
1878 },
1879 .block_body, .final_block_body => {
1880 h.data_hash.update(bytes);
1881 h.data_size += bytes.len;
1882 h.block_remaining -= @intCast(bytes.len);
1883 if (h.block_remaining == 0) {
1884 h.state = if (h.state != .final_block_body) .block_header else .footer;
1885 }
1886 },
1887 .footer => {
1888 if (bytes.len < wanted)
1889 return error.WriteFailed; // footer eos
1890 switch (h.container) {
1891 .raw => {},
1892 .gzip => {
1893 h.footer_hash = mem.readInt(u32, bytes[0..4], .little);
1894 h.footer_size = mem.readInt(u32, bytes[4..8], .little);
1895 },
1896 .zlib => {
1897 h.footer_hash = mem.readInt(u32, bytes[0..4], .big);
1898 },
1899 }
1900 h.state = .end;
1901 },
1902 .end => return error.WriteFailed, // data past end
1903 }
1904 }
1905
1906 w.end = 0;
1907 return Writer.countSplat(data, splat);
1908 }
1909
1910 fn flush(w: *Writer) Writer.Error!void {
1911 defer w.* = .failing; // Clears buffer even if state hasn't reached `end`
1912 _ = try @This().drain(w, &.{""}, 0);
1913 }
2871914 };
2881915
289 var codes: [19]HuffmanEncoder.Code = undefined;
290 var enc: HuffmanEncoder = .{
291 .codes = &codes,
292 .freq_cache = undefined,
293 .bit_count = undefined,
294 .lns = undefined,
295 .lfs = undefined,
1916 var in: Io.Reader = .fixed(input);
1917 const opts: packed struct(u19) {
1918 container: PackedContainer,
1919 buf_len: u17,
1920 } = @bitCast(in.takeLeb128(u19) catch 0);
1921 var output: HashedStoreWriter = .init(&.{}, opts.container.val());
1922 var r_buf: [2 * 65536]u8 = undefined;
1923 var r: Raw = try .init(
1924 &output.writer,
1925 r_buf[0 .. opts.buf_len +% flate.max_window_len],
1926 opts.container.val(),
1927 );
1928
1929 var data_base: u18 = 0;
1930 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
1931 var expected_size: u32 = 0;
1932 var vecs: [32][]const u8 = undefined;
1933 var vecs_n: usize = 0;
1934
1935 while (in.seek != in.end) {
1936 const VecInfo = packed struct(u58) {
1937 output: bool,
1938 /// If set, `data_len` and `splat` are reinterpreted as `capacity`
1939 /// and `preserve_len` respectively and `output` is treated as set.
1940 rebase: bool,
1941 block_aligning_len: bool,
1942 block_aligning_splat: bool,
1943 data_len: u18,
1944 splat: u18,
1945 data_off: u18,
1946 };
1947 var vec_info: VecInfo = @bitCast(in.takeLeb128(u58) catch |e| switch (e) {
1948 error.ReadFailed => unreachable,
1949 error.Overflow, error.EndOfStream => 0,
1950 });
1951
1952 {
1953 const buffered = r.writer.buffered().len + countVec(vecs[0..vecs_n]);
1954 const to_align = mem.alignForwardAnyAlign(usize, buffered, Raw.max_block_size) - buffered;
1955 assert((buffered + to_align) % Raw.max_block_size == 0);
1956
1957 if (vec_info.block_aligning_len) {
1958 vec_info.data_len = @intCast(to_align);
1959 } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and
1960 to_align % vec_info.data_len == 0)
1961 {
1962 vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1;
1963 }
1964 }
1965
1966 var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1;
1967 add_vec: {
1968 if (vec_info.rebase) break :add_vec;
1969 if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) >
1970 10 * (1 << 16))
1971 {
1972 // Skip this vector to avoid this test taking too long.
1973 // 10 maximum sized blocks is choosen as the limit since it is two more
1974 // than the maximum the implementation can output in one drain.
1975 splat = 1;
1976 break :add_vec;
1977 }
1978
1979 vecs[vecs_n] = data_buf[@min(
1980 data_base +% vec_info.data_off,
1981 data_buf.len - vec_info.data_len,
1982 )..][0..vec_info.data_len];
1983
1984 data_base +%= vec_info.data_len +% 3; // extra 3 to help catch aliasing bugs
1985
1986 for (0..splat) |_| expected_hash.update(vecs[vecs_n]);
1987 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;
1988 vecs_n += 1;
1989 }
1990
1991 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or
1992 in.seek == in.end;
1993 if (want_drain and vecs_n != 0) {
1994 try r.writer.writeSplatAll(vecs[0..vecs_n], splat);
1995 vecs_n = 0;
1996 } else assert(splat == 1);
1997
1998 if (vec_info.rebase) {
1999 try r.writer.rebase(vec_info.data_len, @min(
2000 r.writer.buffer.len -| vec_info.data_len,
2001 vec_info.splat,
2002 ));
2003 }
2004 }
2005
2006 try r.writer.flush();
2007 try output.writer.flush();
2008
2009 try std.testing.expectEqual(.end, output.state);
2010 try std.testing.expectEqual(expected_size, output.data_size);
2011 switch (output.data_hash) {
2012 .raw => {},
2013 .gzip => |gz| {
2014 const expected_crc = expected_hash.gzip.crc.final();
2015 try std.testing.expectEqual(expected_crc, gz.crc.final());
2016 try std.testing.expectEqual(expected_crc, output.footer_hash);
2017 try std.testing.expectEqual(expected_size, output.footer_size);
2018 },
2019 .zlib => |zl| {
2020 const expected_adler = expected_hash.zlib.adler;
2021 try std.testing.expectEqual(expected_adler, zl.adler);
2022 try std.testing.expectEqual(expected_adler, output.footer_hash);
2023 },
2024 }
2025}
2026
2027/// Only performs huffman compression on data, does no matching.
2028pub const Huffman = struct {
2029 writer: Writer,
2030 bit_writer: BitWriter,
2031 hasher: flate.Container.Hasher,
2032
2033 const max_tokens: u16 = 65535 - 1; // one is reserved for EOF
2034
2035 /// While there is no minimum buffer size, it is recommended
2036 /// to be at least `flate.max_window_len` to improve compression.
2037 ///
2038 /// It is asserted `output` has a capacity of at least 8 bytes.
2039 pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Huffman {
2040 assert(output.buffer.len > 8);
2041
2042 try output.writeAll(container.header());
2043 return .{
2044 .writer = .{
2045 .buffer = buffer,
2046 .vtable = &.{
2047 .drain = Huffman.drain,
2048 .flush = Huffman.flush,
2049 .rebase = Huffman.rebase,
2050 },
2051 },
2052 .bit_writer = .init(output),
2053 .hasher = .init(container),
2054 };
2055 }
2056
2057 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
2058 {
2059 //std.debug.print("drain {} (buffered)", .{w.buffered().len});
2060 //for (data) |d| std.debug.print("\n\t+ {}", .{d.len});
2061 //std.debug.print(" x {}\n\n", .{splat});
2062 }
2063
2064 const h: *Huffman = @fieldParentPtr("writer", w);
2065 const min_block = @min(w.buffer.len, max_tokens);
2066 const pattern = data[data.len - 1];
2067
2068 const data_bytes = Writer.countSplat(data, splat);
2069 const total_bytes = w.end + data_bytes;
2070 var rem_bytes = total_bytes;
2071 var rem_splat = splat;
2072 var rem_data = data;
2073 var rem_data_elem: []const u8 = w.buffered();
2074
2075 assert(rem_bytes > min_block);
2076 while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final
2077 // also, it handles the case of `min_block` being zero (no buffer)
2078 const block_size: u16 = @min(rem_bytes, max_tokens);
2079 rem_bytes -= block_size;
2080
2081 // Count frequencies
2082 comptime assert(max_tokens != 65535);
2083 var freqs: [257]u16 = @splat(0);
2084 freqs[256] = 1;
2085
2086 const start_splat = rem_splat;
2087 const start_data = rem_data;
2088 const start_data_elem = rem_data_elem;
2089
2090 var block_limit: Io.Limit = .limited(block_size);
2091 while (true) {
2092 const bytes = block_limit.sliceConst(rem_data_elem);
2093 const is_pattern = rem_splat != splat and bytes.len == pattern.len;
2094
2095 const mul = if (!is_pattern) 1 else @intFromEnum(block_limit) / pattern.len;
2096 assert(mul != 0);
2097 if (is_pattern) assert(mul <= rem_splat + 1); // one more for `rem_data`
2098
2099 for (bytes) |b| freqs[b] += @intCast(mul);
2100 rem_data_elem = rem_data_elem[bytes.len..];
2101 block_limit = block_limit.subtract(bytes.len * mul).?;
2102
2103 if (rem_data_elem.len == 0) {
2104 rem_data_elem = rem_data[0];
2105 if (rem_data.len != 1) {
2106 rem_data = rem_data[1..];
2107 } else if (rem_splat >= mul) {
2108 // if the counter was not the pattern, `mul` is always one, otherwise,
2109 // `mul` contains `rem_data`, however one more needs subtracted anyways
2110 // since the next pattern is also being taken.
2111 rem_splat -= mul;
2112 } else {
2113 // All of `data` has been consumed.
2114 assert(block_limit == .nothing);
2115 assert(rem_bytes == 0);
2116 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
2117 rem_data = undefined;
2118 rem_data_elem = undefined;
2119 rem_splat = undefined;
2120 }
2121 }
2122 if (block_limit == .nothing) break;
2123 }
2124
2125 // Output block
2126 rem_splat = start_splat;
2127 rem_data = start_data;
2128 rem_data_elem = start_data_elem;
2129 block_limit = .limited(block_size);
2130
2131 var codes_buf: CodesBuf = .init;
2132 if (try h.outputHeader(&freqs, &codes_buf, block_size, false)) |table| {
2133 while (true) {
2134 const bytes = block_limit.sliceConst(rem_data_elem);
2135 rem_data_elem = rem_data_elem[bytes.len..];
2136 block_limit = block_limit.subtract(bytes.len).?;
2137
2138 h.hasher.update(bytes);
2139 for (bytes) |b| {
2140 try h.bit_writer.write(table.codes[b], table.bits[b]);
2141 }
2142
2143 if (rem_data_elem.len == 0) {
2144 rem_data_elem = rem_data[0];
2145 if (rem_data.len != 1) {
2146 rem_data = rem_data[1..];
2147 } else if (rem_splat != 0) {
2148 rem_splat -= 1;
2149 } else {
2150 // All of `data` has been consumed.
2151 assert(block_limit == .nothing);
2152 assert(rem_bytes == 0);
2153 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
2154 rem_data = undefined;
2155 rem_data_elem = undefined;
2156 rem_splat = undefined;
2157 }
2158 }
2159 if (block_limit == .nothing) break;
2160 }
2161 try h.bit_writer.write(table.codes[256], table.bits[256]);
2162 } else while (true) {
2163 // Store block
2164
2165 // Write data that is not a full vector element
2166 const in_pattern = rem_splat != splat;
2167 const vec_elem_i, const in_data =
2168 @subWithOverflow(data.len - (rem_data.len - @intFromBool(in_pattern)), 1);
2169 const is_elem = in_data == 0 and data[vec_elem_i].len == rem_data_elem.len;
2170
2171 if (!is_elem or rem_data_elem.len > @intFromEnum(block_limit)) {
2172 block_limit = block_limit.subtract(rem_data_elem.len) orelse {
2173 try h.bit_writer.output.writeAll(rem_data_elem[0..@intFromEnum(block_limit)]);
2174 h.hasher.update(rem_data_elem[0..@intFromEnum(block_limit)]);
2175 rem_data_elem = rem_data_elem[@intFromEnum(block_limit)..];
2176 assert(rem_data_elem.len != 0);
2177 break;
2178 };
2179 try h.bit_writer.output.writeAll(rem_data_elem);
2180 h.hasher.update(rem_data_elem);
2181 } else {
2182 // Put `rem_data_elem` back in `rem_data`
2183 if (!in_pattern) {
2184 rem_data = data[vec_elem_i..];
2185 } else {
2186 rem_splat += 1;
2187 }
2188 }
2189 rem_data_elem = undefined; // it is always updated below
2190
2191 // Send through as much of the original vector as possible
2192 var vec_n: usize = 0;
2193 var vlimit = block_limit;
2194 const vec_splat = while (rem_data[vec_n..].len != 1) {
2195 vlimit = vlimit.subtract(rem_data[vec_n].len) orelse break 1;
2196 vec_n += 1;
2197 } else vec_splat: {
2198 // For `pattern.len == 0`, the value of `vec_splat` does not matter.
2199 const vec_splat = @intFromEnum(vlimit) / @max(1, pattern.len);
2200 if (pattern.len != 0) assert(vec_splat <= rem_splat + 1);
2201 vlimit = vlimit.subtract(pattern.len * vec_splat).?;
2202 vec_n += 1;
2203 break :vec_splat vec_splat;
2204 };
2205
2206 const n = if (vec_n != 0) n: {
2207 assert(@intFromEnum(block_limit) - @intFromEnum(vlimit) ==
2208 Writer.countSplat(rem_data[0..vec_n], vec_splat));
2209 break :n try h.bit_writer.output.writeSplat(rem_data[0..vec_n], vec_splat);
2210 } else 0; // Still go into the case below to advance the vector
2211 block_limit = block_limit.subtract(n).?;
2212 var consumed: Io.Limit = .limited(n);
2213
2214 while (rem_data.len != 1) {
2215 const elem = rem_data[0];
2216 rem_data = rem_data[1..];
2217 consumed = consumed.subtract(elem.len) orelse {
2218 h.hasher.update(elem[0..@intFromEnum(consumed)]);
2219 rem_data_elem = elem[@intFromEnum(consumed)..];
2220 break;
2221 };
2222 h.hasher.update(elem);
2223 } else {
2224 if (pattern.len == 0) {
2225 // All of `data` has been consumed. However, the general
2226 // case below does not work since it divides by zero.
2227 assert(consumed == .nothing);
2228 assert(block_limit == .nothing);
2229 assert(rem_bytes == 0);
2230 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
2231 rem_splat = undefined;
2232 rem_data = undefined;
2233 rem_data_elem = undefined;
2234 break;
2235 }
2236
2237 const splatted = @intFromEnum(consumed) / pattern.len;
2238 const partial = @intFromEnum(consumed) % pattern.len;
2239 for (0..splatted) |_| h.hasher.update(pattern);
2240 h.hasher.update(pattern[0..partial]);
2241
2242 const taken_splat = splatted + 1;
2243 if (rem_splat >= taken_splat) {
2244 rem_splat -= taken_splat;
2245 rem_data_elem = pattern[partial..];
2246 } else {
2247 // All of `data` has been consumed.
2248 assert(partial == 0);
2249 assert(block_limit == .nothing);
2250 assert(rem_bytes == 0);
2251 // Since `rem_bytes` and `block_limit` are zero, these won't be used.
2252 rem_data = undefined;
2253 rem_data_elem = undefined;
2254 rem_splat = undefined;
2255 }
2256 }
2257
2258 if (block_limit == .nothing) break;
2259 }
2260 }
2261
2262 if (rem_bytes > data_bytes) {
2263 assert(rem_bytes - data_bytes == rem_data_elem.len);
2264 assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]);
2265 }
2266 return w.consume(total_bytes - rem_bytes);
2267 }
2268
2269 fn flush(w: *Writer) Writer.Error!void {
2270 defer w.* = .failing;
2271 const h: *Huffman = @fieldParentPtr("writer", w);
2272 try Huffman.rebaseInner(w, 0, w.buffer.len, true);
2273 try h.bit_writer.output.rebase(0, 1);
2274 h.bit_writer.byteAlign();
2275 try h.hasher.writeFooter(h.bit_writer.output);
2276 }
2277
2278 fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
2279 errdefer w.* = .failing;
2280 try Huffman.rebaseInner(w, preserve, capacity, false);
2281 }
2282
2283 fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
2284 const h: *Huffman = @fieldParentPtr("writer", w);
2285 assert(preserve + capacity <= w.buffer.len);
2286 if (eos) assert(capacity == w.buffer.len);
2287
2288 const preserved = @min(w.end, preserve);
2289 var remaining = w.buffer[0 .. w.end - preserved];
2290 while (remaining.len > max_tokens) { // not >= so there is always a block down below
2291 const bytes = remaining[0..max_tokens];
2292 remaining = remaining[max_tokens..];
2293 try h.outputBytes(bytes, false);
2294 }
2295
2296 // eos check required for empty block
2297 if (w.buffer.len - (remaining.len + preserved) < capacity or eos) {
2298 const bytes = remaining;
2299 remaining = &.{};
2300 try h.outputBytes(bytes, eos);
2301 }
2302
2303 _ = w.consume(w.end - preserved - remaining.len);
2304 }
2305
2306 fn outputBytes(h: *Huffman, bytes: []const u8, eos: bool) Writer.Error!void {
2307 comptime assert(max_tokens != 65535);
2308 assert(bytes.len <= max_tokens);
2309 var freqs: [257]u16 = @splat(0);
2310 freqs[256] = 1;
2311 for (bytes) |b| freqs[b] += 1;
2312 h.hasher.update(bytes);
2313
2314 var codes_buf: CodesBuf = .init;
2315 if (try h.outputHeader(&freqs, &codes_buf, @intCast(bytes.len), eos)) |table| {
2316 for (bytes) |b| {
2317 try h.bit_writer.write(table.codes[b], table.bits[b]);
2318 }
2319 try h.bit_writer.write(table.codes[256], table.bits[256]);
2320 } else {
2321 try h.bit_writer.output.writeAll(bytes);
2322 }
2323 }
2324
2325 const CodesBuf = struct {
2326 dyn_codes: [258]u16,
2327 dyn_bits: [258]u4,
2328
2329 pub const init: CodesBuf = .{
2330 .dyn_codes = @as([257]u16, undefined) ++ .{0},
2331 .dyn_bits = @as([257]u4, @splat(0)) ++ .{1},
2332 };
2962333 };
297 enc.generate(freqs[0..], 7);
298
299 try testing.expectEqual(@as(u32, 141), enc.bitLength(freqs[0..]));
300
301 try testing.expectEqual(@as(usize, 3), enc.codes[0].len);
302 try testing.expectEqual(@as(usize, 6), enc.codes[1].len);
303 try testing.expectEqual(@as(usize, 6), enc.codes[2].len);
304 try testing.expectEqual(@as(usize, 5), enc.codes[3].len);
305 try testing.expectEqual(@as(usize, 3), enc.codes[4].len);
306 try testing.expectEqual(@as(usize, 2), enc.codes[5].len);
307 try testing.expectEqual(@as(usize, 2), enc.codes[6].len);
308 try testing.expectEqual(@as(usize, 6), enc.codes[7].len);
309 try testing.expectEqual(@as(usize, 0), enc.codes[8].len);
310 try testing.expectEqual(@as(usize, 0), enc.codes[9].len);
311 try testing.expectEqual(@as(usize, 0), enc.codes[10].len);
312 try testing.expectEqual(@as(usize, 0), enc.codes[11].len);
313 try testing.expectEqual(@as(usize, 0), enc.codes[12].len);
314 try testing.expectEqual(@as(usize, 0), enc.codes[13].len);
315 try testing.expectEqual(@as(usize, 0), enc.codes[14].len);
316 try testing.expectEqual(@as(usize, 0), enc.codes[15].len);
317 try testing.expectEqual(@as(usize, 6), enc.codes[16].len);
318 try testing.expectEqual(@as(usize, 5), enc.codes[17].len);
319 try testing.expectEqual(@as(usize, 3), enc.codes[18].len);
320
321 try testing.expectEqual(@as(u16, 0x0), enc.codes[5].code);
322 try testing.expectEqual(@as(u16, 0x2), enc.codes[6].code);
323 try testing.expectEqual(@as(u16, 0x1), enc.codes[0].code);
324 try testing.expectEqual(@as(u16, 0x5), enc.codes[4].code);
325 try testing.expectEqual(@as(u16, 0x3), enc.codes[18].code);
326 try testing.expectEqual(@as(u16, 0x7), enc.codes[3].code);
327 try testing.expectEqual(@as(u16, 0x17), enc.codes[17].code);
328 try testing.expectEqual(@as(u16, 0x0f), enc.codes[1].code);
329 try testing.expectEqual(@as(u16, 0x2f), enc.codes[2].code);
330 try testing.expectEqual(@as(u16, 0x1f), enc.codes[7].code);
331 try testing.expectEqual(@as(u16, 0x3f), enc.codes[16].code);
2334
2335 /// Returns null if the block is stored.
2336 fn outputHeader(
2337 h: *Huffman,
2338 freqs: *const [257]u16,
2339 buf: *CodesBuf,
2340 bytes: u16,
2341 eos: bool,
2342 ) Writer.Error!?struct {
2343 codes: *const [257]u16,
2344 bits: *const [257]u4,
2345 } {
2346 assert(freqs[256] == 1);
2347 const dyn_codes_bitsize, _ = huffman.build(
2348 freqs,
2349 buf.dyn_codes[0..257],
2350 buf.dyn_bits[0..257],
2351 15,
2352 true,
2353 );
2354
2355 var clen_values: [258]u8 = undefined;
2356 var clen_extra: [258]u8 = undefined;
2357 var clen_freqs: [19]u16 = @splat(0);
2358 const clen_len, const clen_extra_bitsize = buildClen(
2359 &buf.dyn_bits,
2360 &clen_values,
2361 &clen_extra,
2362 &clen_freqs,
2363 );
2364
2365 var clen_codes: [19]u16 = undefined;
2366 var clen_bits: [19]u4 = @splat(0);
2367 const clen_codes_bitsize, _ = huffman.build(
2368 &clen_freqs,
2369 &clen_codes,
2370 &clen_bits,
2371 7,
2372 false,
2373 );
2374 const hclen = clenHlen(clen_freqs);
2375
2376 const dynamic_bitsize = @as(u32, 14) +
2377 (4 + @as(u6, hclen)) * 3 + clen_codes_bitsize + clen_extra_bitsize +
2378 dyn_codes_bitsize;
2379 const fixed_bitsize = n: {
2380 const freq7 = 1; // eos
2381 var freq9: u16 = 0;
2382 for (freqs[144..256]) |f| freq9 += f;
2383 const freq8: u16 = bytes - freq9;
2384 break :n @as(u32, freq7) * 7 + @as(u32, freq8) * 8 + @as(u32, freq9) * 9;
2385 };
2386 const stored_bitsize = n: {
2387 const stored_align_bits = -%(h.bit_writer.buffered_n +% 3);
2388 break :n stored_align_bits + @as(u32, 32) + @as(u32, bytes) * 8;
2389 };
2390
2391 //std.debug.print("@ {}{{{}}} ", .{ h.bit_writer.output.end, h.bit_writer.buffered_n });
2392 //std.debug.print("#{} -> s {} f {} d {}\n", .{ bytes, stored_bitsize, fixed_bitsize, dynamic_bitsize });
2393
2394 if (stored_bitsize <= @min(dynamic_bitsize, fixed_bitsize)) {
2395 try h.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3);
2396 try h.bit_writer.output.rebase(0, 5);
2397 h.bit_writer.byteAlign();
2398 h.bit_writer.output.writeInt(u16, bytes, .little) catch unreachable;
2399 h.bit_writer.output.writeInt(u16, ~bytes, .little) catch unreachable;
2400 return null;
2401 }
2402
2403 if (fixed_bitsize <= dynamic_bitsize) {
2404 try h.bit_writer.write(BlockHeader.int(.{ .final = eos, .kind = .fixed }), 3);
2405 return .{
2406 .codes = token.fixed_lit_codes[0..257],
2407 .bits = token.fixed_lit_bits[0..257],
2408 };
2409 } else {
2410 try h.bit_writer.write(BlockHeader.Dynamic.int(.{
2411 .regular = .{ .final = eos, .kind = .dynamic },
2412 .hlit = 0,
2413 .hdist = 0,
2414 .hclen = hclen,
2415 }), 17);
2416 try h.bit_writer.writeClen(
2417 hclen,
2418 clen_values[0..clen_len],
2419 clen_extra[0..clen_len],
2420 clen_codes,
2421 clen_bits,
2422 );
2423 return .{ .codes = buf.dyn_codes[0..257], .bits = buf.dyn_bits[0..257] };
2424 }
2425 }
2426};
2427
2428test Huffman {
2429 const fbufs = try testingFreqBufs();
2430 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);
2431 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});
2432}
2433
2434/// This function is derived from `testFuzzedRawInput` with a few changes for fuzzing `Huffman`.
2435fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, input: []const u8) !void {
2436 var in: Io.Reader = .fixed(input);
2437 const opts: packed struct(u19) {
2438 container: PackedContainer,
2439 buf_len: u17,
2440 } = @bitCast(in.takeLeb128(u19) catch 0);
2441 var flate_buf: [2 * 65536]u8 = undefined;
2442 var flate_w: Writer = .fixed(&flate_buf);
2443 var h_buf: [2 * 65536]u8 = undefined;
2444 var h: Huffman = try .init(
2445 &flate_w,
2446 h_buf[0 .. opts.buf_len +% flate.max_window_len],
2447 opts.container.val(),
2448 );
2449
2450 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
2451 var expected_size: u32 = 0;
2452 var vecs: [32][]const u8 = undefined;
2453 var vecs_n: usize = 0;
2454
2455 while (in.seek != in.end) {
2456 const VecInfo = packed struct(u55) {
2457 output: bool,
2458 /// If set, `data_len` and `splat` are reinterpreted as `capacity`
2459 /// and `preserve_len` respectively and `output` is treated as set.
2460 rebase: bool,
2461 block_aligning_len: bool,
2462 block_aligning_splat: bool,
2463 data_off_hi: u8,
2464 random_data: u1,
2465 data_len: u16,
2466 splat: u18,
2467 /// This is less useful as each value is part of the same gradient 'step'
2468 data_off_lo: u8,
2469 };
2470 var vec_info: VecInfo = @bitCast(in.takeLeb128(u55) catch |e| switch (e) {
2471 error.ReadFailed => unreachable,
2472 error.Overflow, error.EndOfStream => 0,
2473 });
2474
2475 {
2476 const buffered = h.writer.buffered().len + countVec(vecs[0..vecs_n]);
2477 const to_align = mem.alignForwardAnyAlign(usize, buffered, Huffman.max_tokens) - buffered;
2478 assert((buffered + to_align) % Huffman.max_tokens == 0);
2479
2480 if (vec_info.block_aligning_len) {
2481 vec_info.data_len = @intCast(to_align);
2482 } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and
2483 to_align % vec_info.data_len == 0)
2484 {
2485 vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1;
2486 }
2487 }
2488
2489 var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1;
2490 add_vec: {
2491 if (vec_info.rebase) break :add_vec;
2492 if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) > 4 * (1 << 16)) {
2493 // Skip this vector to avoid this test taking too long.
2494 splat = 1;
2495 break :add_vec;
2496 }
2497
2498 const data_buf = &fbufs[vec_info.random_data];
2499 vecs[vecs_n] = data_buf[@min(
2500 (@as(u16, vec_info.data_off_hi) << 8) | vec_info.data_off_lo,
2501 data_buf.len - vec_info.data_len,
2502 )..][0..vec_info.data_len];
2503
2504 for (0..splat) |_| expected_hash.update(vecs[vecs_n]);
2505 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;
2506 vecs_n += 1;
2507 }
2508
2509 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or
2510 in.seek == in.end;
2511 if (want_drain and vecs_n != 0) {
2512 var n = h.writer.buffered().len + Writer.countSplat(vecs[0..vecs_n], splat);
2513 const oos = h.writer.writeSplatAll(vecs[0..vecs_n], splat) == error.WriteFailed;
2514 n -= h.writer.buffered().len;
2515 const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable;
2516 const lim = flate_w.end + 6 * block_lim + n; // 6 since block header may span two bytes
2517 if (flate_w.end > lim) return error.OverheadTooLarge;
2518 if (oos) return;
2519
2520 vecs_n = 0;
2521 } else assert(splat == 1);
2522
2523 if (vec_info.rebase) {
2524 const old_end = flate_w.end;
2525 var n = h.writer.buffered().len;
2526 const oos = h.writer.rebase(vec_info.data_len, @min(
2527 h.writer.buffer.len -| vec_info.data_len,
2528 vec_info.splat,
2529 )) == error.WriteFailed;
2530 n -= h.writer.buffered().len;
2531 const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable;
2532 const lim = old_end + 6 * block_lim + n; // 6 since block header may span two bytes
2533 if (flate_w.end > lim) return error.OverheadTooLarge;
2534 if (oos) return;
2535 }
2536 }
2537
2538 {
2539 const old_end = flate_w.end;
2540 const n = h.writer.buffered().len;
2541 const oos = h.writer.flush() == error.WriteFailed;
2542 assert(h.writer.buffered().len == 0);
2543 const block_lim = @max(1, math.divCeil(usize, n, Huffman.max_tokens) catch unreachable);
2544 const lim = old_end + 6 * block_lim + n + opts.container.val().footerSize();
2545 if (flate_w.end > lim) return error.OverheadTooLarge;
2546 if (oos) return;
2547 }
2548
2549 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
3322550}
lib/std/compress/flate/Decompress.zig+156-235
......@@ -7,11 +7,10 @@ const Reader = std.Io.Reader;
77const Container = flate.Container;
88
99const Decompress = @This();
10const Token = @import("Token.zig");
10const token = @import("token.zig");
1111
1212input: *Reader,
13next_bits: Bits,
14remaining_bits: std.math.Log2Int(Bits),
13consumed_bits: u3,
1514
1615reader: Reader,
1716
......@@ -25,8 +24,6 @@ state: State,
2524
2625err: ?Error,
2726
28const Bits = usize;
29
3027const BlockType = enum(u2) {
3128 stored = 0,
3229 fixed = 1,
......@@ -39,6 +36,8 @@ const State = union(enum) {
3936 block_header,
4037 stored_block: u16,
4138 fixed_block,
39 fixed_block_literal: u8,
40 fixed_block_match: u16,
4241 dynamic_block,
4342 dynamic_block_literal: u8,
4443 dynamic_block_match: u16,
......@@ -87,8 +86,7 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
8786 .end = 0,
8887 },
8988 .input = input,
90 .next_bits = 0,
91 .remaining_bits = 0,
89 .consumed_bits = 0,
9290 .container_metadata = .init(container),
9391 .lit_dec = .{},
9492 .dst_dec = .{},
......@@ -183,27 +181,25 @@ fn streamIndirectInner(d: *Decompress) Reader.Error!usize {
183181 return 0;
184182}
185183
186fn decodeLength(self: *Decompress, code: u8) !u16 {
187 if (code > 28) return error.InvalidCode;
188 const ml = Token.matchLength(code);
189 return if (ml.extra_bits == 0) // 0 - 5 extra bits
190 ml.base
191 else
192 ml.base + try self.takeBitsRuntime(ml.extra_bits);
184fn decodeLength(self: *Decompress, code_int: u5) !u16 {
185 if (code_int > 28) return error.InvalidCode;
186 const l: token.LenCode = .fromInt(code_int);
187 const base = l.base();
188 const extra = l.extraBits();
189 return token.min_length + (base | try self.takeBits(extra));
193190}
194191
195fn decodeDistance(self: *Decompress, code: u8) !u16 {
196 if (code > 29) return error.InvalidCode;
197 const md = Token.matchDistance(code);
198 return if (md.extra_bits == 0) // 0 - 13 extra bits
199 md.base
200 else
201 md.base + try self.takeBitsRuntime(md.extra_bits);
192fn decodeDistance(self: *Decompress, code_int: u5) !u16 {
193 if (code_int > 29) return error.InvalidCode;
194 const d: token.DistCode = .fromInt(code_int);
195 const base = d.base();
196 const extra = d.extraBits();
197 return token.min_distance + (base | try self.takeBits(extra));
202198}
203199
204// Decode code length symbol to code length. Writes decoded length into
205// lens slice starting at position pos. Returns number of positions
206// advanced.
200/// Decode code length symbol to code length. Writes decoded length into
201/// lens slice starting at position pos. Returns number of positions
202/// advanced.
207203fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usize {
208204 if (pos >= lens.len)
209205 return error.InvalidDynamicBlockHeader;
......@@ -217,7 +213,7 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz
217213 16 => {
218214 // Copy the previous code length 3 - 6 times.
219215 // The next 2 bits indicate repeat length
220 const n: u8 = @as(u8, try self.takeBits(u2)) + 3;
216 const n: u8 = @as(u8, try self.takeIntBits(u2)) + 3;
221217 if (pos == 0 or pos + n > lens.len)
222218 return error.InvalidDynamicBlockHeader;
223219 for (0..n) |i| {
......@@ -226,17 +222,17 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz
226222 return n;
227223 },
228224 // Repeat a code length of 0 for 3 - 10 times. (3 bits of length)
229 17 => return @as(u8, try self.takeBits(u3)) + 3,
225 17 => return @as(u8, try self.takeIntBits(u3)) + 3,
230226 // Repeat a code length of 0 for 11 - 138 times (7 bits of length)
231 18 => return @as(u8, try self.takeBits(u7)) + 11,
227 18 => return @as(u8, try self.takeIntBits(u7)) + 11,
232228 else => return error.InvalidDynamicBlockHeader,
233229 }
234230}
235231
236232fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol {
237233 // Maximum code len is 15 bits.
238 const sym = try decoder.find(@bitReverse(try self.peekBits(u15)));
239 try self.tossBits(sym.code_bits);
234 const sym = try decoder.find(@bitReverse(try self.peekIntBitsShort(u15)));
235 try self.tossBitsShort(sym.code_bits);
240236 return sym;
241237}
242238
......@@ -320,11 +316,11 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
320316 .raw => continue :sw .block_header,
321317 },
322318 .block_header => {
323 d.final_block = (try d.takeBits(u1)) != 0;
324 const block_type: BlockType = @enumFromInt(try d.takeBits(u2));
319 d.final_block = (try d.takeIntBits(u1)) != 0;
320 const block_type: BlockType = @enumFromInt(try d.takeIntBits(u2));
325321 switch (block_type) {
326322 .stored => {
327 d.alignBitsDiscarding();
323 d.alignBitsForward();
328324 // everything after this is byte aligned in stored block
329325 const len = try in.takeInt(u16, .little);
330326 const nlen = try in.takeInt(u16, .little);
......@@ -333,17 +329,17 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
333329 },
334330 .fixed => continue :sw .fixed_block,
335331 .dynamic => {
336 const hlit: u16 = @as(u16, try d.takeBits(u5)) + 257; // number of ll code entries present - 257
337 const hdist: u16 = @as(u16, try d.takeBits(u5)) + 1; // number of distance code entries - 1
338 const hclen: u8 = @as(u8, try d.takeBits(u4)) + 4; // hclen + 4 code lengths are encoded
332 const hlit: u16 = @as(u16, try d.takeIntBits(u5)) + 257; // number of ll code entries present - 257
333 const hdist: u16 = @as(u16, try d.takeIntBits(u5)) + 1; // number of distance code entries - 1
334 const hclen: u8 = @as(u8, try d.takeIntBits(u4)) + 4; // hclen + 4 code lengths are encoded
339335
340336 if (hlit > 286 or hdist > 30)
341337 return error.InvalidDynamicBlockHeader;
342338
343339 // lengths for code lengths
344340 var cl_lens: [19]u4 = @splat(0);
345 for (flate.HuffmanEncoder.codegen_order[0..hclen]) |i| {
346 cl_lens[i] = try d.takeBits(u3);
341 for (token.codegen_order[0..hclen]) |i| {
342 cl_lens[i] = try d.takeIntBits(u3);
347343 }
348344 var cl_dec: CodegenDecoder = .{};
349345 try cl_dec.generate(&cl_lens);
......@@ -352,9 +348,9 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
352348 var dec_lens: [286 + 30]u4 = @splat(0);
353349 var pos: usize = 0;
354350 while (pos < hlit + hdist) {
355 const peeked = @bitReverse(try d.peekBits(u7));
351 const peeked = @bitReverse(try d.peekIntBitsShort(u7));
356352 const sym = try cl_dec.find(peeked);
357 try d.tossBits(sym.code_bits);
353 try d.tossBitsShort(sym.code_bits);
358354 pos += try d.dynamicCodeLength(sym.symbol, &dec_lens, pos);
359355 }
360356 if (pos > hlit + hdist) {
......@@ -373,9 +369,12 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
373369 }
374370 },
375371 .stored_block => |remaining_len| {
376 const out = try w.writableSliceGreedyPreserve(flate.history_len, 1);
372 const out: []u8 = if (remaining != 0)
373 try w.writableSliceGreedyPreserve(flate.history_len, 1)
374 else
375 &.{};
377376 var limited_out: [1][]u8 = .{limit.min(.limited(remaining_len)).slice(out)};
378 const n = try d.input.readVec(&limited_out);
377 const n = try in.readVec(&limited_out);
379378 if (remaining_len - n == 0) {
380379 d.state = if (d.final_block) .protocol_footer else .block_header;
381380 } else {
......@@ -389,8 +388,14 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
389388 const code = try d.readFixedCode();
390389 switch (code) {
391390 0...255 => {
392 try w.writeBytePreserve(flate.history_len, @intCast(code));
393 remaining -= 1;
391 if (remaining != 0) {
392 @branchHint(.likely);
393 try w.writeBytePreserve(flate.history_len, @intCast(code));
394 remaining -= 1;
395 } else {
396 d.state = .{ .fixed_block_literal = @intCast(code) };
397 return @intFromEnum(limit) - remaining;
398 }
394399 },
395400 256 => {
396401 d.state = if (d.final_block) .protocol_footer else .block_header;
......@@ -400,9 +405,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
400405 // Handles fixed block non literal (length) code.
401406 // Length code is followed by 5 bits of distance code.
402407 const length = try d.decodeLength(@intCast(code - 257));
403 const distance = try d.decodeDistance(@bitReverse(try d.takeBits(u5)));
404 try writeMatch(w, length, distance);
405 remaining -= length;
408 continue :sw .{ .fixed_block_match = length };
406409 },
407410 else => return error.InvalidCode,
408411 }
......@@ -410,6 +413,24 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
410413 d.state = .fixed_block;
411414 return @intFromEnum(limit) - remaining;
412415 },
416 .fixed_block_literal => |symbol| {
417 assert(remaining != 0);
418 remaining -= 1;
419 try w.writeBytePreserve(flate.history_len, symbol);
420 continue :sw .fixed_block;
421 },
422 .fixed_block_match => |length| {
423 if (remaining >= length) {
424 @branchHint(.likely);
425 const distance = try d.decodeDistance(@bitReverse(try d.takeIntBits(u5)));
426 try writeMatch(w, length, distance);
427 remaining -= length;
428 continue :sw .fixed_block;
429 } else {
430 d.state = .{ .fixed_block_match = length };
431 return @intFromEnum(limit) - remaining;
432 }
433 },
413434 .dynamic_block => {
414435 // In larger archives most blocks are usually dynamic, so
415436 // decompression performance depends on this logic.
......@@ -429,7 +450,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
429450 },
430451 .match => {
431452 // Decode match backreference <length, distance>
432 const length = try d.decodeLength(sym.symbol);
453 const length = try d.decodeLength(@intCast(sym.symbol));
433454 continue :sw .{ .dynamic_block_match = length };
434455 },
435456 .end_of_block => {
......@@ -449,7 +470,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
449470 @branchHint(.likely);
450471 remaining -= length;
451472 const dsm = try d.decodeSymbol(&d.dst_dec);
452 const distance = try d.decodeDistance(dsm.symbol);
473 const distance = try d.decodeDistance(@intCast(dsm.symbol));
453474 try writeMatch(w, length, distance);
454475 continue :sw .dynamic_block;
455476 } else {
......@@ -458,23 +479,16 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
458479 }
459480 },
460481 .protocol_footer => {
482 d.alignBitsForward();
461483 switch (d.container_metadata) {
462484 .gzip => |*gzip| {
463 d.alignBitsDiscarding();
464 gzip.* = .{
465 .crc = try in.takeInt(u32, .little),
466 .count = try in.takeInt(u32, .little),
467 };
485 gzip.crc = try in.takeInt(u32, .little);
486 gzip.count = try in.takeInt(u32, .little);
468487 },
469488 .zlib => |*zlib| {
470 d.alignBitsDiscarding();
471 zlib.* = .{
472 .adler = try in.takeInt(u32, .little),
473 };
474 },
475 .raw => {
476 d.alignBitsPreserving();
489 zlib.adler = try in.takeInt(u32, .big);
477490 },
491 .raw => {},
478492 }
479493 d.state = .end;
480494 return @intFromEnum(limit) - remaining;
......@@ -487,10 +501,10 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
487501/// back from current write position, and `length` of bytes.
488502fn writeMatch(w: *Writer, length: u16, distance: u16) !void {
489503 if (w.end < distance) return error.InvalidMatch;
490 if (length < Token.base_length) return error.InvalidMatch;
491 if (length > Token.max_length) return error.InvalidMatch;
492 if (distance < Token.min_distance) return error.InvalidMatch;
493 if (distance > Token.max_distance) return error.InvalidMatch;
504 if (length < token.min_length) return error.InvalidMatch;
505 if (length > token.max_length) return error.InvalidMatch;
506 if (distance < token.min_distance) return error.InvalidMatch;
507 if (distance > token.max_distance) return error.InvalidMatch;
494508
495509 // This is not a @memmove; it intentionally repeats patterns caused by
496510 // iterating one byte at a time.
......@@ -500,137 +514,71 @@ fn writeMatch(w: *Writer, length: u16, distance: u16) !void {
500514 for (dest, src) |*d, s| d.* = s;
501515}
502516
503fn takeBits(d: *Decompress, comptime U: type) !U {
504 const remaining_bits = d.remaining_bits;
505 const next_bits = d.next_bits;
506 if (remaining_bits >= @bitSizeOf(U)) {
507 const u: U = @truncate(next_bits);
508 d.next_bits = next_bits >> @bitSizeOf(U);
509 d.remaining_bits = remaining_bits - @bitSizeOf(U);
510 return u;
511 }
512 const in = d.input;
513 const next_int = in.takeInt(Bits, .little) catch |err| switch (err) {
514 error.ReadFailed => return error.ReadFailed,
515 error.EndOfStream => return takeBitsEnding(d, U),
517fn peekBits(d: *Decompress, n: u4) !u16 {
518 const bits = d.input.peekInt(u32, .little) catch |e| return switch (e) {
519 error.ReadFailed => error.ReadFailed,
520 error.EndOfStream => d.peekBitsEnding(n),
516521 };
517 const needed_bits = @bitSizeOf(U) - remaining_bits;
518 const u: U = @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits);
519 d.next_bits = next_int >> needed_bits;
520 d.remaining_bits = @intCast(@bitSizeOf(Bits) - @as(usize, needed_bits));
521 return u;
522 const mask = @shlExact(@as(u16, 1), n) - 1;
523 return @intCast((bits >> d.consumed_bits) & mask);
522524}
523525
524fn takeBitsEnding(d: *Decompress, comptime U: type) !U {
525 const remaining_bits = d.remaining_bits;
526 const next_bits = d.next_bits;
527 const in = d.input;
528 const n = in.bufferedLen();
529 assert(n < @sizeOf(Bits));
530 const needed_bits = @bitSizeOf(U) - remaining_bits;
531 if (n * 8 < needed_bits) return error.EndOfStream;
532 const next_int = in.takeVarInt(Bits, .little, n) catch |err| switch (err) {
533 error.ReadFailed => return error.ReadFailed,
534 error.EndOfStream => unreachable,
535 };
536 const u: U = @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits);
537 d.next_bits = next_int >> needed_bits;
538 d.remaining_bits = @intCast(n * 8 - @as(usize, needed_bits));
539 return u;
526fn peekBitsEnding(d: *Decompress, n: u4) !u16 {
527 @branchHint(.unlikely);
528
529 const left = d.input.buffered();
530 if (left.len * 8 - d.consumed_bits < n) return error.EndOfStream;
531 const bits = std.mem.readVarInt(u32, left, .little);
532 const mask = @shlExact(@as(u16, 1), n) - 1;
533 return @intCast((bits >> d.consumed_bits) & mask);
540534}
541535
542fn peekBits(d: *Decompress, comptime U: type) !U {
543 const remaining_bits = d.remaining_bits;
544 const next_bits = d.next_bits;
545 if (remaining_bits >= @bitSizeOf(U)) return @truncate(next_bits);
546 const in = d.input;
547 const next_int = in.peekInt(Bits, .little) catch |err| switch (err) {
548 error.ReadFailed => return error.ReadFailed,
549 error.EndOfStream => return peekBitsEnding(d, U),
550 };
551 const needed_bits = @bitSizeOf(U) - remaining_bits;
552 return @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits);
536/// Safe only after `peekBits` has been called with a greater or equal `n` value.
537fn tossBits(d: *Decompress, n: u4) void {
538 d.input.toss((@as(u8, n) + d.consumed_bits) / 8);
539 d.consumed_bits +%= @truncate(n);
553540}
554541
555fn peekBitsEnding(d: *Decompress, comptime U: type) !U {
556 const remaining_bits = d.remaining_bits;
557 const next_bits = d.next_bits;
558 const in = d.input;
559 var u: Bits = 0;
560 var remaining_needed_bits = @bitSizeOf(U) - remaining_bits;
561 var i: usize = 0;
562 while (remaining_needed_bits > 0) {
563 const peeked = in.peek(i + 1) catch |err| switch (err) {
564 error.ReadFailed => return error.ReadFailed,
565 error.EndOfStream => break,
566 };
567 u |= @as(Bits, peeked[i]) << @intCast(i * 8);
568 remaining_needed_bits -|= 8;
569 i += 1;
570 }
571 if (remaining_bits == 0 and i == 0) return error.EndOfStream;
572 return @truncate((u << remaining_bits) | next_bits);
573}
574
575fn tossBits(d: *Decompress, n: u4) !void {
576 const remaining_bits = d.remaining_bits;
577 const next_bits = d.next_bits;
578 if (remaining_bits >= n) {
579 d.next_bits = next_bits >> n;
580 d.remaining_bits = remaining_bits - n;
581 } else {
582 const in = d.input;
583 const next_int = in.takeInt(Bits, .little) catch |err| switch (err) {
584 error.ReadFailed => return error.ReadFailed,
585 error.EndOfStream => return tossBitsEnding(d, n),
586 };
587 const needed_bits = n - remaining_bits;
588 d.next_bits = next_int >> needed_bits;
589 d.remaining_bits = @intCast(@bitSizeOf(Bits) - @as(usize, needed_bits));
590 }
542fn takeBits(d: *Decompress, n: u4) !u16 {
543 const bits = try d.peekBits(n);
544 d.tossBits(n);
545 return bits;
591546}
592547
593fn tossBitsEnding(d: *Decompress, n: u4) !void {
594 const remaining_bits = d.remaining_bits;
595 const in = d.input;
596 const buffered_n = in.bufferedLen();
597 if (buffered_n == 0) return error.EndOfStream;
598 assert(buffered_n < @sizeOf(Bits));
599 const needed_bits = n - remaining_bits;
600 const next_int = in.takeVarInt(Bits, .little, buffered_n) catch |err| switch (err) {
601 error.ReadFailed => return error.ReadFailed,
602 error.EndOfStream => unreachable,
548fn alignBitsForward(d: *Decompress) void {
549 d.input.toss(@intFromBool(d.consumed_bits != 0));
550 d.consumed_bits = 0;
551}
552
553fn peekBitsShort(d: *Decompress, n: u4) !u16 {
554 const bits = d.input.peekInt(u32, .little) catch |e| return switch (e) {
555 error.ReadFailed => error.ReadFailed,
556 error.EndOfStream => d.peekBitsShortEnding(n),
603557 };
604 d.next_bits = next_int >> needed_bits;
605 d.remaining_bits = @intCast(@as(usize, buffered_n) * 8 -| @as(usize, needed_bits));
558 const mask = @shlExact(@as(u16, 1), n) - 1;
559 return @intCast((bits >> d.consumed_bits) & mask);
606560}
607561
608fn takeBitsRuntime(d: *Decompress, n: u4) !u16 {
609 const x = try peekBits(d, u16);
610 const mask: u16 = (@as(u16, 1) << n) - 1;
611 const u: u16 = @as(u16, @truncate(x)) & mask;
612 try tossBits(d, n);
613 return u;
562fn peekBitsShortEnding(d: *Decompress, n: u4) !u16 {
563 @branchHint(.unlikely);
564
565 const left = d.input.buffered();
566 const bits = std.mem.readVarInt(u32, left, .little);
567 const mask = @shlExact(@as(u16, 1), n) - 1;
568 return @intCast((bits >> d.consumed_bits) & mask);
614569}
615570
616fn alignBitsDiscarding(d: *Decompress) void {
617 const remaining_bits = d.remaining_bits;
618 if (remaining_bits == 0) return;
619 const n_bytes = remaining_bits / 8;
620 const in = d.input;
621 in.seek -= n_bytes;
622 d.remaining_bits = 0;
623 d.next_bits = 0;
571fn tossBitsShort(d: *Decompress, n: u4) !void {
572 if (d.input.bufferedLen() * 8 + d.consumed_bits < n) return error.EndOfStream;
573 d.tossBits(n);
624574}
625575
626fn alignBitsPreserving(d: *Decompress) void {
627 const remaining_bits: usize = d.remaining_bits;
628 if (remaining_bits == 0) return;
629 const n_bytes = (remaining_bits + 7) / 8;
630 const in = d.input;
631 in.seek -= n_bytes;
632 d.remaining_bits = 0;
633 d.next_bits = 0;
576fn takeIntBits(d: *Decompress, T: type) !T {
577 return @intCast(try d.takeBits(@bitSizeOf(T)));
578}
579
580fn peekIntBitsShort(d: *Decompress, T: type) !T {
581 return @intCast(try d.peekBitsShort(@bitSizeOf(T)));
634582}
635583
636584/// Reads first 7 bits, and then maybe 1 or 2 more to get full 7,8 or 9 bit code.
......@@ -646,12 +594,12 @@ fn alignBitsPreserving(d: *Decompress) void {
646594/// 280 - 287 8 11000000 through
647595/// 11000111
648596fn readFixedCode(d: *Decompress) !u16 {
649 const code7 = @bitReverse(try d.takeBits(u7));
597 const code7 = @bitReverse(try d.takeIntBits(u7));
650598 return switch (code7) {
651599 0...0b0010_111 => @as(u16, code7) + 256,
652 0b0010_111 + 1...0b1011_111 => (@as(u16, code7) << 1) + @as(u16, try d.takeBits(u1)) - 0b0011_0000,
653 0b1011_111 + 1...0b1100_011 => (@as(u16, code7 - 0b1100000) << 1) + try d.takeBits(u1) + 280,
654 else => (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, @bitReverse(try d.takeBits(u2))) + 144,
600 0b0010_111 + 1...0b1011_111 => (@as(u16, code7) << 1) + @as(u16, try d.takeIntBits(u1)) - 0b0011_0000,
601 0b1011_111 + 1...0b1100_011 => (@as(u16, code7 - 0b1100000) << 1) + try d.takeIntBits(u1) + 280,
602 else => (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, @bitReverse(try d.takeIntBits(u2))) + 144,
655603 };
656604}
657605
......@@ -807,7 +755,7 @@ fn HuffmanDecoder(
807755 return self.findLinked(code, sym.next);
808756 }
809757
810 inline fn findLinked(self: *Self, code: u16, start: u16) !Symbol {
758 fn findLinked(self: *Self, code: u16, start: u16) !Symbol {
811759 var pos = start;
812760 while (pos > 0) {
813761 const sym = self.symbols[pos];
......@@ -898,57 +846,30 @@ test "init/find" {
898846}
899847
900848test "encode/decode literals" {
901 var codes: [flate.HuffmanEncoder.max_num_frequencies]flate.HuffmanEncoder.Code = undefined;
902 for (1..286) |j| { // for all different number of codes
903 var enc: flate.HuffmanEncoder = .{
904 .codes = &codes,
905 .freq_cache = undefined,
906 .bit_count = undefined,
907 .lns = undefined,
908 .lfs = undefined,
909 };
910 // create frequencies
911 var freq = [_]u16{0} ** 286;
912 freq[256] = 1; // ensure we have end of block code
913 for (&freq, 1..) |*f, i| {
914 if (i % j == 0)
915 f.* = @intCast(i);
916 }
917
918 // encoder from frequencies
919 enc.generate(&freq, 15);
920
921 // get code_lens from encoder
922 var code_lens = [_]u4{0} ** 286;
923 for (code_lens, 0..) |_, i| {
924 code_lens[i] = @intCast(enc.codes[i].len);
925 }
926 // generate decoder from code lens
927 var dec: LiteralDecoder = .{};
928 try dec.generate(&code_lens);
929
930 // expect decoder code to match original encoder code
931 for (dec.symbols) |s| {
932 if (s.code_bits == 0) continue;
933 const c_code: u16 = @bitReverse(@as(u15, @intCast(s.code)));
934 const symbol: u16 = switch (s.kind) {
935 .literal => s.symbol,
936 .end_of_block => 256,
937 .match => @as(u16, s.symbol) + 257,
938 };
939
940 const c = enc.codes[symbol];
941 try testing.expect(c.code == c_code);
942 }
943
944 // find each symbol by code
945 for (enc.codes) |c| {
946 if (c.len == 0) continue;
947
948 const s_code: u15 = @bitReverse(@as(u15, @intCast(c.code)));
949 const s = try dec.find(s_code);
950 try testing.expect(s.code == s_code);
951 try testing.expect(s.code_bits == c.len);
849 // Check that the example in RFC 1951 section 3.2.2 works (plus some zeroes)
850 const max_bits = 5;
851 var decoder: HuffmanDecoder(16, max_bits, 3) = .{};
852 try decoder.generate(&.{ 3, 3, 3, 3, 0, 0, 3, 2, 4, 4 });
853
854 inline for (0.., .{
855 @as(u3, 0b010),
856 @as(u3, 0b011),
857 @as(u3, 0b100),
858 @as(u3, 0b101),
859 @as(u0, 0),
860 @as(u0, 0),
861 @as(u3, 0b110),
862 @as(u2, 0b00),
863 @as(u4, 0b1110),
864 @as(u4, 0b1111),
865 }) |i, code| {
866 const bits = @bitSizeOf(@TypeOf(code));
867 if (bits == 0) continue;
868 for (0..1 << (max_bits - bits)) |extra| {
869 const full = (@as(u16, code) << (max_bits - bits)) | @as(u16, @intCast(extra));
870 const symbol = try decoder.find(full);
871 try testing.expectEqual(i, symbol.symbol);
872 try testing.expectEqual(bits, symbol.code_bits);
952873 }
953874 }
954875}
lib/std/compress/flate/HuffmanEncoder.zig deleted-463
......@@ -1,463 +0,0 @@
1const HuffmanEncoder = @This();
2const std = @import("std");
3const assert = std.debug.assert;
4const testing = std.testing;
5
6codes: []Code,
7// Reusable buffer with the longest possible frequency table.
8freq_cache: [max_num_frequencies + 1]LiteralNode,
9bit_count: [17]u32,
10lns: []LiteralNode, // sorted by literal, stored to avoid repeated allocation in generate
11lfs: []LiteralNode, // sorted by frequency, stored to avoid repeated allocation in generate
12
13pub const LiteralNode = struct {
14 literal: u16,
15 freq: u16,
16
17 pub fn max() LiteralNode {
18 return .{
19 .literal = std.math.maxInt(u16),
20 .freq = std.math.maxInt(u16),
21 };
22 }
23};
24
25pub const Code = struct {
26 code: u16 = 0,
27 len: u16 = 0,
28};
29
30/// The odd order in which the codegen code sizes are written.
31pub const codegen_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
32/// The number of codegen codes.
33pub const codegen_code_count = 19;
34
35/// The largest distance code.
36pub const distance_code_count = 30;
37
38/// Maximum number of literals.
39pub const max_num_lit = 286;
40
41/// Max number of frequencies used for a Huffman Code
42/// Possible lengths are codegen_code_count (19), distance_code_count (30) and max_num_lit (286).
43/// The largest of these is max_num_lit.
44pub const max_num_frequencies = max_num_lit;
45
46/// Biggest block size for uncompressed block.
47pub const max_store_block_size = 65535;
48/// The special code used to mark the end of a block.
49pub const end_block_marker = 256;
50
51/// Update this Huffman Code object to be the minimum code for the specified frequency count.
52///
53/// freq An array of frequencies, in which frequency[i] gives the frequency of literal i.
54/// max_bits The maximum number of bits to use for any literal.
55pub fn generate(self: *HuffmanEncoder, freq: []u16, max_bits: u32) void {
56 var list = self.freq_cache[0 .. freq.len + 1];
57 // Number of non-zero literals
58 var count: u32 = 0;
59 // Set list to be the set of all non-zero literals and their frequencies
60 for (freq, 0..) |f, i| {
61 if (f != 0) {
62 list[count] = LiteralNode{ .literal = @as(u16, @intCast(i)), .freq = f };
63 count += 1;
64 } else {
65 list[count] = LiteralNode{ .literal = 0x00, .freq = 0 };
66 self.codes[i].len = 0;
67 }
68 }
69 list[freq.len] = LiteralNode{ .literal = 0x00, .freq = 0 };
70
71 list = list[0..count];
72 if (count <= 2) {
73 // Handle the small cases here, because they are awkward for the general case code. With
74 // two or fewer literals, everything has bit length 1.
75 for (list, 0..) |node, i| {
76 // "list" is in order of increasing literal value.
77 self.codes[node.literal] = .{
78 .code = @intCast(i),
79 .len = 1,
80 };
81 }
82 return;
83 }
84 self.lfs = list;
85 std.mem.sort(LiteralNode, self.lfs, {}, byFreq);
86
87 // Get the number of literals for each bit count
88 const bit_count = self.bitCounts(list, max_bits);
89 // And do the assignment
90 self.assignEncodingAndSize(bit_count, list);
91}
92
93pub fn bitLength(self: *HuffmanEncoder, freq: []u16) u32 {
94 var total: u32 = 0;
95 for (freq, 0..) |f, i| {
96 if (f != 0) {
97 total += @as(u32, @intCast(f)) * @as(u32, @intCast(self.codes[i].len));
98 }
99 }
100 return total;
101}
102
103/// Return the number of literals assigned to each bit size in the Huffman encoding
104///
105/// This method is only called when list.len >= 3
106/// The cases of 0, 1, and 2 literals are handled by special case code.
107///
108/// list: An array of the literals with non-zero frequencies
109/// and their associated frequencies. The array is in order of increasing
110/// frequency, and has as its last element a special element with frequency
111/// `math.maxInt(i32)`
112///
113/// max_bits: The maximum number of bits that should be used to encode any literal.
114/// Must be less than 16.
115///
116/// Returns an integer array in which array[i] indicates the number of literals
117/// that should be encoded in i bits.
118fn bitCounts(self: *HuffmanEncoder, list: []LiteralNode, max_bits_to_use: usize) []u32 {
119 var max_bits = max_bits_to_use;
120 const n = list.len;
121 const max_bits_limit = 16;
122
123 assert(max_bits < max_bits_limit);
124
125 // The tree can't have greater depth than n - 1, no matter what. This
126 // saves a little bit of work in some small cases
127 max_bits = @min(max_bits, n - 1);
128
129 // Create information about each of the levels.
130 // A bogus "Level 0" whose sole purpose is so that
131 // level1.prev.needed == 0. This makes level1.next_pair_freq
132 // be a legitimate value that never gets chosen.
133 var levels: [max_bits_limit]LevelInfo = std.mem.zeroes([max_bits_limit]LevelInfo);
134 // leaf_counts[i] counts the number of literals at the left
135 // of ancestors of the rightmost node at level i.
136 // leaf_counts[i][j] is the number of literals at the left
137 // of the level j ancestor.
138 var leaf_counts: [max_bits_limit][max_bits_limit]u32 = @splat(@splat(0));
139
140 {
141 var level = @as(u32, 1);
142 while (level <= max_bits) : (level += 1) {
143 // For every level, the first two items are the first two characters.
144 // We initialize the levels as if we had already figured this out.
145 levels[level] = LevelInfo{
146 .level = level,
147 .last_freq = list[1].freq,
148 .next_char_freq = list[2].freq,
149 .next_pair_freq = list[0].freq + list[1].freq,
150 .needed = 0,
151 };
152 leaf_counts[level][level] = 2;
153 if (level == 1) {
154 levels[level].next_pair_freq = std.math.maxInt(i32);
155 }
156 }
157 }
158
159 // We need a total of 2*n - 2 items at top level and have already generated 2.
160 levels[max_bits].needed = 2 * @as(u32, @intCast(n)) - 4;
161
162 {
163 var level = max_bits;
164 while (true) {
165 var l = &levels[level];
166 if (l.next_pair_freq == std.math.maxInt(i32) and l.next_char_freq == std.math.maxInt(i32)) {
167 // We've run out of both leaves and pairs.
168 // End all calculations for this level.
169 // To make sure we never come back to this level or any lower level,
170 // set next_pair_freq impossibly large.
171 l.needed = 0;
172 levels[level + 1].next_pair_freq = std.math.maxInt(i32);
173 level += 1;
174 continue;
175 }
176
177 const prev_freq = l.last_freq;
178 if (l.next_char_freq < l.next_pair_freq) {
179 // The next item on this row is a leaf node.
180 const next = leaf_counts[level][level] + 1;
181 l.last_freq = l.next_char_freq;
182 // Lower leaf_counts are the same of the previous node.
183 leaf_counts[level][level] = next;
184 if (next >= list.len) {
185 l.next_char_freq = LiteralNode.max().freq;
186 } else {
187 l.next_char_freq = list[next].freq;
188 }
189 } else {
190 // The next item on this row is a pair from the previous row.
191 // next_pair_freq isn't valid until we generate two
192 // more values in the level below
193 l.last_freq = l.next_pair_freq;
194 // Take leaf counts from the lower level, except counts[level] remains the same.
195 @memcpy(leaf_counts[level][0..level], leaf_counts[level - 1][0..level]);
196 levels[l.level - 1].needed = 2;
197 }
198
199 l.needed -= 1;
200 if (l.needed == 0) {
201 // We've done everything we need to do for this level.
202 // Continue calculating one level up. Fill in next_pair_freq
203 // of that level with the sum of the two nodes we've just calculated on
204 // this level.
205 if (l.level == max_bits) {
206 // All done!
207 break;
208 }
209 levels[l.level + 1].next_pair_freq = prev_freq + l.last_freq;
210 level += 1;
211 } else {
212 // If we stole from below, move down temporarily to replenish it.
213 while (levels[level - 1].needed > 0) {
214 level -= 1;
215 if (level == 0) {
216 break;
217 }
218 }
219 }
220 }
221 }
222
223 // Somethings is wrong if at the end, the top level is null or hasn't used
224 // all of the leaves.
225 assert(leaf_counts[max_bits][max_bits] == n);
226
227 var bit_count = self.bit_count[0 .. max_bits + 1];
228 var bits: u32 = 1;
229 const counts = &leaf_counts[max_bits];
230 {
231 var level = max_bits;
232 while (level > 0) : (level -= 1) {
233 // counts[level] gives the number of literals requiring at least "bits"
234 // bits to encode.
235 bit_count[bits] = counts[level] - counts[level - 1];
236 bits += 1;
237 if (level == 0) {
238 break;
239 }
240 }
241 }
242 return bit_count;
243}
244
245/// Look at the leaves and assign them a bit count and an encoding as specified
246/// in RFC 1951 3.2.2
247fn assignEncodingAndSize(self: *HuffmanEncoder, bit_count: []u32, list_arg: []LiteralNode) void {
248 var code = @as(u16, 0);
249 var list = list_arg;
250
251 for (bit_count, 0..) |bits, n| {
252 code <<= 1;
253 if (n == 0 or bits == 0) {
254 continue;
255 }
256 // The literals list[list.len-bits] .. list[list.len-bits]
257 // are encoded using "bits" bits, and get the values
258 // code, code + 1, .... The code values are
259 // assigned in literal order (not frequency order).
260 const chunk = list[list.len - @as(u32, @intCast(bits)) ..];
261
262 self.lns = chunk;
263 std.mem.sort(LiteralNode, self.lns, {}, byLiteral);
264
265 for (chunk) |node| {
266 self.codes[node.literal] = .{
267 .code = bitReverse(u16, code, @as(u5, @intCast(n))),
268 .len = @as(u16, @intCast(n)),
269 };
270 code += 1;
271 }
272 list = list[0 .. list.len - @as(u32, @intCast(bits))];
273 }
274}
275
276fn byFreq(context: void, a: LiteralNode, b: LiteralNode) bool {
277 _ = context;
278 if (a.freq == b.freq) {
279 return a.literal < b.literal;
280 }
281 return a.freq < b.freq;
282}
283
284/// Describes the state of the constructed tree for a given depth.
285const LevelInfo = struct {
286 /// Our level. for better printing
287 level: u32,
288 /// The frequency of the last node at this level
289 last_freq: u32,
290 /// The frequency of the next character to add to this level
291 next_char_freq: u32,
292 /// The frequency of the next pair (from level below) to add to this level.
293 /// Only valid if the "needed" value of the next lower level is 0.
294 next_pair_freq: u32,
295 /// The number of chains remaining to generate for this level before moving
296 /// up to the next level
297 needed: u32,
298};
299
300fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) bool {
301 _ = context;
302 return a.literal < b.literal;
303}
304
305/// Reverse bit-by-bit a N-bit code.
306fn bitReverse(comptime T: type, value: T, n: usize) T {
307 const r = @bitReverse(value);
308 return r >> @as(std.math.Log2Int(T), @intCast(@typeInfo(T).int.bits - n));
309}
310
311test bitReverse {
312 const ReverseBitsTest = struct {
313 in: u16,
314 bit_count: u5,
315 out: u16,
316 };
317
318 const reverse_bits_tests = [_]ReverseBitsTest{
319 .{ .in = 1, .bit_count = 1, .out = 1 },
320 .{ .in = 1, .bit_count = 2, .out = 2 },
321 .{ .in = 1, .bit_count = 3, .out = 4 },
322 .{ .in = 1, .bit_count = 4, .out = 8 },
323 .{ .in = 1, .bit_count = 5, .out = 16 },
324 .{ .in = 17, .bit_count = 5, .out = 17 },
325 .{ .in = 257, .bit_count = 9, .out = 257 },
326 .{ .in = 29, .bit_count = 5, .out = 23 },
327 };
328
329 for (reverse_bits_tests) |h| {
330 const v = bitReverse(u16, h.in, h.bit_count);
331 try std.testing.expectEqual(h.out, v);
332 }
333}
334
335/// Generates a HuffmanCode corresponding to the fixed literal table
336pub fn fixedLiteralEncoder(codes: *[max_num_frequencies]Code) HuffmanEncoder {
337 var h: HuffmanEncoder = undefined;
338 h.codes = codes;
339 var ch: u16 = 0;
340
341 while (ch < max_num_frequencies) : (ch += 1) {
342 var bits: u16 = undefined;
343 var size: u16 = undefined;
344 switch (ch) {
345 0...143 => {
346 // size 8, 000110000 .. 10111111
347 bits = ch + 48;
348 size = 8;
349 },
350 144...255 => {
351 // size 9, 110010000 .. 111111111
352 bits = ch + 400 - 144;
353 size = 9;
354 },
355 256...279 => {
356 // size 7, 0000000 .. 0010111
357 bits = ch - 256;
358 size = 7;
359 },
360 else => {
361 // size 8, 11000000 .. 11000111
362 bits = ch + 192 - 280;
363 size = 8;
364 },
365 }
366 h.codes[ch] = .{ .code = bitReverse(u16, bits, @as(u5, @intCast(size))), .len = size };
367 }
368 return h;
369}
370
371pub fn fixedDistanceEncoder(codes: *[distance_code_count]Code) HuffmanEncoder {
372 var h: HuffmanEncoder = undefined;
373 h.codes = codes;
374 for (h.codes, 0..) |_, ch| {
375 h.codes[ch] = .{ .code = bitReverse(u16, @as(u16, @intCast(ch)), 5), .len = 5 };
376 }
377 return h;
378}
379
380pub fn huffmanDistanceEncoder(codes: *[distance_code_count]Code) HuffmanEncoder {
381 var distance_freq: [distance_code_count]u16 = @splat(0);
382 distance_freq[0] = 1;
383 // huff_distance is a static distance encoder used for huffman only encoding.
384 // It can be reused since we will not be encoding distance values.
385 var h: HuffmanEncoder = .{};
386 h.codes = codes;
387 h.generate(distance_freq[0..], 15);
388 return h;
389}
390
391test "generate a Huffman code for the fixed literal table specific to Deflate" {
392 var codes: [max_num_frequencies]Code = undefined;
393 const enc: HuffmanEncoder = .fixedLiteralEncoder(&codes);
394 for (enc.codes) |c| {
395 switch (c.len) {
396 7 => {
397 const v = @bitReverse(@as(u7, @intCast(c.code)));
398 try testing.expect(v <= 0b0010111);
399 },
400 8 => {
401 const v = @bitReverse(@as(u8, @intCast(c.code)));
402 try testing.expect((v >= 0b000110000 and v <= 0b10111111) or
403 (v >= 0b11000000 and v <= 11000111));
404 },
405 9 => {
406 const v = @bitReverse(@as(u9, @intCast(c.code)));
407 try testing.expect(v >= 0b110010000 and v <= 0b111111111);
408 },
409 else => unreachable,
410 }
411 }
412}
413
414test "generate a Huffman code for the 30 possible relative distances (LZ77 distances) of Deflate" {
415 var codes: [distance_code_count]Code = undefined;
416 const enc = fixedDistanceEncoder(&codes);
417 for (enc.codes) |c| {
418 const v = @bitReverse(@as(u5, @intCast(c.code)));
419 try testing.expect(v <= 29);
420 try testing.expect(c.len == 5);
421 }
422}
423
424pub const fixed_codes = [_]u8{
425 0b00001100, 0b10001100, 0b01001100, 0b11001100, 0b00101100, 0b10101100, 0b01101100, 0b11101100,
426 0b00011100, 0b10011100, 0b01011100, 0b11011100, 0b00111100, 0b10111100, 0b01111100, 0b11111100,
427 0b00000010, 0b10000010, 0b01000010, 0b11000010, 0b00100010, 0b10100010, 0b01100010, 0b11100010,
428 0b00010010, 0b10010010, 0b01010010, 0b11010010, 0b00110010, 0b10110010, 0b01110010, 0b11110010,
429 0b00001010, 0b10001010, 0b01001010, 0b11001010, 0b00101010, 0b10101010, 0b01101010, 0b11101010,
430 0b00011010, 0b10011010, 0b01011010, 0b11011010, 0b00111010, 0b10111010, 0b01111010, 0b11111010,
431 0b00000110, 0b10000110, 0b01000110, 0b11000110, 0b00100110, 0b10100110, 0b01100110, 0b11100110,
432 0b00010110, 0b10010110, 0b01010110, 0b11010110, 0b00110110, 0b10110110, 0b01110110, 0b11110110,
433 0b00001110, 0b10001110, 0b01001110, 0b11001110, 0b00101110, 0b10101110, 0b01101110, 0b11101110,
434 0b00011110, 0b10011110, 0b01011110, 0b11011110, 0b00111110, 0b10111110, 0b01111110, 0b11111110,
435 0b00000001, 0b10000001, 0b01000001, 0b11000001, 0b00100001, 0b10100001, 0b01100001, 0b11100001,
436 0b00010001, 0b10010001, 0b01010001, 0b11010001, 0b00110001, 0b10110001, 0b01110001, 0b11110001,
437 0b00001001, 0b10001001, 0b01001001, 0b11001001, 0b00101001, 0b10101001, 0b01101001, 0b11101001,
438 0b00011001, 0b10011001, 0b01011001, 0b11011001, 0b00111001, 0b10111001, 0b01111001, 0b11111001,
439 0b00000101, 0b10000101, 0b01000101, 0b11000101, 0b00100101, 0b10100101, 0b01100101, 0b11100101,
440 0b00010101, 0b10010101, 0b01010101, 0b11010101, 0b00110101, 0b10110101, 0b01110101, 0b11110101,
441 0b00001101, 0b10001101, 0b01001101, 0b11001101, 0b00101101, 0b10101101, 0b01101101, 0b11101101,
442 0b00011101, 0b10011101, 0b01011101, 0b11011101, 0b00111101, 0b10111101, 0b01111101, 0b11111101,
443 0b00010011, 0b00100110, 0b01001110, 0b10011010, 0b00111100, 0b01100101, 0b11101010, 0b10110100,
444 0b11101001, 0b00110011, 0b01100110, 0b11001110, 0b10011010, 0b00111101, 0b01100111, 0b11101110,
445 0b10111100, 0b11111001, 0b00001011, 0b00010110, 0b00101110, 0b01011010, 0b10111100, 0b01100100,
446 0b11101001, 0b10110010, 0b11100101, 0b00101011, 0b01010110, 0b10101110, 0b01011010, 0b10111101,
447 0b01100110, 0b11101101, 0b10111010, 0b11110101, 0b00011011, 0b00110110, 0b01101110, 0b11011010,
448 0b10111100, 0b01100101, 0b11101011, 0b10110110, 0b11101101, 0b00111011, 0b01110110, 0b11101110,
449 0b11011010, 0b10111101, 0b01100111, 0b11101111, 0b10111110, 0b11111101, 0b00000111, 0b00001110,
450 0b00011110, 0b00111010, 0b01111100, 0b11100100, 0b11101000, 0b10110001, 0b11100011, 0b00100111,
451 0b01001110, 0b10011110, 0b00111010, 0b01111101, 0b11100110, 0b11101100, 0b10111001, 0b11110011,
452 0b00010111, 0b00101110, 0b01011110, 0b10111010, 0b01111100, 0b11100101, 0b11101010, 0b10110101,
453 0b11101011, 0b00110111, 0b01101110, 0b11011110, 0b10111010, 0b01111101, 0b11100111, 0b11101110,
454 0b10111101, 0b11111011, 0b00001111, 0b00011110, 0b00111110, 0b01111010, 0b11111100, 0b11100100,
455 0b11101001, 0b10110011, 0b11100111, 0b00101111, 0b01011110, 0b10111110, 0b01111010, 0b11111101,
456 0b11100110, 0b11101101, 0b10111011, 0b11110111, 0b00011111, 0b00111110, 0b01111110, 0b11111010,
457 0b11111100, 0b11100101, 0b11101011, 0b10110111, 0b11101111, 0b00111111, 0b01111110, 0b11111110,
458 0b11111010, 0b11111101, 0b11100111, 0b11101111, 0b10111111, 0b11111111, 0b00000000, 0b00100000,
459 0b00001000, 0b00001100, 0b10000001, 0b11000010, 0b11100000, 0b00001000, 0b00100100, 0b00001010,
460 0b10001101, 0b11000001, 0b11100010, 0b11110000, 0b00000100, 0b00100010, 0b10001001, 0b01001100,
461 0b10100001, 0b11010010, 0b11101000, 0b00000011, 0b10000011, 0b01000011, 0b11000011, 0b00100011,
462 0b10100011,
463};
lib/std/compress/flate/Lookup.zig deleted-130
......@@ -1,130 +0,0 @@
1//! Lookup of the previous locations for the same 4 byte data. Works on hash of
2//! 4 bytes data. Head contains position of the first match for each hash. Chain
3//! points to the previous position of the same hash given the current location.
4
5const std = @import("std");
6const testing = std.testing;
7const expect = testing.expect;
8const flate = @import("../flate.zig");
9const Token = @import("Token.zig");
10
11const Lookup = @This();
12
13const prime4 = 0x9E3779B1; // 4 bytes prime number 2654435761
14const chain_len = 2 * flate.history_len;
15
16pub const bits = 15;
17pub const len = 1 << bits;
18pub const shift = 32 - bits;
19
20// Maps hash => first position
21head: [len]u16 = [_]u16{0} ** len,
22// Maps position => previous positions for the same hash value
23chain: [chain_len]u16 = [_]u16{0} ** (chain_len),
24
25// Calculates hash of the 4 bytes from data.
26// Inserts `pos` position of that hash in the lookup tables.
27// Returns previous location with the same hash value.
28pub fn add(self: *Lookup, data: []const u8, pos: u16) u16 {
29 if (data.len < 4) return 0;
30 const h = hash(data[0..4]);
31 return self.set(h, pos);
32}
33
34// Returns previous location with the same hash value given the current
35// position.
36pub fn prev(self: *Lookup, pos: u16) u16 {
37 return self.chain[pos];
38}
39
40fn set(self: *Lookup, h: u32, pos: u16) u16 {
41 const p = self.head[h];
42 self.head[h] = pos;
43 self.chain[pos] = p;
44 return p;
45}
46
47// Slide all positions in head and chain for `n`
48pub fn slide(self: *Lookup, n: u16) void {
49 for (&self.head) |*v| {
50 v.* -|= n;
51 }
52 var i: usize = 0;
53 while (i < n) : (i += 1) {
54 self.chain[i] = self.chain[i + n] -| n;
55 }
56}
57
58// Add `len` 4 bytes hashes from `data` into lookup.
59// Position of the first byte is `pos`.
60pub fn bulkAdd(self: *Lookup, data: []const u8, length: u16, pos: u16) void {
61 if (length == 0 or data.len < Token.min_length) {
62 return;
63 }
64 var hb =
65 @as(u32, data[3]) |
66 @as(u32, data[2]) << 8 |
67 @as(u32, data[1]) << 16 |
68 @as(u32, data[0]) << 24;
69 _ = self.set(hashu(hb), pos);
70
71 var i = pos;
72 for (4..@min(length + 3, data.len)) |j| {
73 hb = (hb << 8) | @as(u32, data[j]);
74 i += 1;
75 _ = self.set(hashu(hb), i);
76 }
77}
78
79// Calculates hash of the first 4 bytes of `b`.
80fn hash(b: *const [4]u8) u32 {
81 return hashu(@as(u32, b[3]) |
82 @as(u32, b[2]) << 8 |
83 @as(u32, b[1]) << 16 |
84 @as(u32, b[0]) << 24);
85}
86
87fn hashu(v: u32) u32 {
88 return @intCast((v *% prime4) >> shift);
89}
90
91test add {
92 const data = [_]u8{
93 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
94 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
95 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
96 0x01, 0x02, 0x03,
97 };
98
99 var h: Lookup = .{};
100 for (data, 0..) |_, i| {
101 const p = h.add(data[i..], @intCast(i));
102 if (i >= 8 and i < 24) {
103 try expect(p == i - 8);
104 } else {
105 try expect(p == 0);
106 }
107 }
108
109 const v = Lookup.hash(data[2 .. 2 + 4]);
110 try expect(h.head[v] == 2 + 16);
111 try expect(h.chain[2 + 16] == 2 + 8);
112 try expect(h.chain[2 + 8] == 2);
113}
114
115test bulkAdd {
116 const data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
117
118 // one by one
119 var h: Lookup = .{};
120 for (data, 0..) |_, i| {
121 _ = h.add(data[i..], @intCast(i));
122 }
123
124 // in bulk
125 var bh: Lookup = .{};
126 bh.bulkAdd(data, data.len, 0);
127
128 try testing.expectEqualSlices(u16, &h.head, &bh.head);
129 try testing.expectEqualSlices(u16, &h.chain, &bh.chain);
130}
lib/std/compress/flate/Token.zig deleted-333
......@@ -1,333 +0,0 @@
1//! Token cat be literal: single byte of data or match; reference to the slice of
2//! data in the same stream represented with <length, distance>. Where length
3//! can be 3 - 258 bytes, and distance 1 - 32768 bytes.
4//!
5const std = @import("std");
6const assert = std.debug.assert;
7const print = std.debug.print;
8const expect = std.testing.expect;
9
10const Token = @This();
11
12pub const Kind = enum(u1) {
13 literal,
14 match,
15};
16
17// Distance range 1 - 32768, stored in dist as 0 - 32767 (fits u15)
18dist: u15 = 0,
19// Length range 3 - 258, stored in len_lit as 0 - 255 (fits u8)
20len_lit: u8 = 0,
21kind: Kind = .literal,
22
23pub const base_length = 3; // smallest match length per the RFC section 3.2.5
24pub const min_length = 4; // min length used in this algorithm
25pub const max_length = 258;
26
27pub const min_distance = 1;
28pub const max_distance = std.compress.flate.history_len;
29
30pub fn literal(t: Token) u8 {
31 return t.len_lit;
32}
33
34pub fn distance(t: Token) u16 {
35 return @as(u16, t.dist) + min_distance;
36}
37
38pub fn length(t: Token) u16 {
39 return @as(u16, t.len_lit) + base_length;
40}
41
42pub fn initLiteral(lit: u8) Token {
43 return .{ .kind = .literal, .len_lit = lit };
44}
45
46// distance range 1 - 32768, stored in dist as 0 - 32767 (u15)
47// length range 3 - 258, stored in len_lit as 0 - 255 (u8)
48pub fn initMatch(dist: u16, len: u16) Token {
49 assert(len >= min_length and len <= max_length);
50 assert(dist >= min_distance and dist <= max_distance);
51 return .{
52 .kind = .match,
53 .dist = @intCast(dist - min_distance),
54 .len_lit = @intCast(len - base_length),
55 };
56}
57
58pub fn eql(t: Token, o: Token) bool {
59 return t.kind == o.kind and
60 t.dist == o.dist and
61 t.len_lit == o.len_lit;
62}
63
64pub fn lengthCode(t: Token) u16 {
65 return match_lengths[match_lengths_index[t.len_lit]].code;
66}
67
68pub fn lengthEncoding(t: Token) MatchLength {
69 var c = match_lengths[match_lengths_index[t.len_lit]];
70 c.extra_length = t.len_lit - c.base_scaled;
71 return c;
72}
73
74// Returns the distance code corresponding to a specific distance.
75// Distance code is in range: 0 - 29.
76pub fn distanceCode(t: Token) u8 {
77 var dist: u16 = t.dist;
78 if (dist < match_distances_index.len) {
79 return match_distances_index[dist];
80 }
81 dist >>= 7;
82 if (dist < match_distances_index.len) {
83 return match_distances_index[dist] + 14;
84 }
85 dist >>= 7;
86 return match_distances_index[dist] + 28;
87}
88
89pub fn distanceEncoding(t: Token) MatchDistance {
90 var c = match_distances[t.distanceCode()];
91 c.extra_distance = t.dist - c.base_scaled;
92 return c;
93}
94
95pub fn lengthExtraBits(code: u32) u8 {
96 return match_lengths[code - length_codes_start].extra_bits;
97}
98
99pub fn matchLength(code: u8) MatchLength {
100 return match_lengths[code];
101}
102
103pub fn matchDistance(code: u8) MatchDistance {
104 return match_distances[code];
105}
106
107pub fn distanceExtraBits(code: u32) u8 {
108 return match_distances[code].extra_bits;
109}
110
111pub fn show(t: Token) void {
112 if (t.kind == .literal) {
113 print("L('{c}'), ", .{t.literal()});
114 } else {
115 print("M({d}, {d}), ", .{ t.distance(), t.length() });
116 }
117}
118
119// Returns index in match_lengths table for each length in range 0-255.
120const match_lengths_index = [_]u8{
121 0, 1, 2, 3, 4, 5, 6, 7, 8, 8,
122 9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
123 13, 13, 13, 13, 14, 14, 14, 14, 15, 15,
124 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
125 17, 17, 17, 17, 17, 17, 17, 17, 18, 18,
126 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
127 19, 19, 19, 19, 20, 20, 20, 20, 20, 20,
128 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
129 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
130 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
131 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
132 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
133 23, 23, 23, 23, 23, 23, 23, 23, 24, 24,
134 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
135 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
136 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
137 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
138 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
139 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
140 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
141 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
142 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
143 26, 26, 26, 26, 27, 27, 27, 27, 27, 27,
144 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
145 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
146 27, 27, 27, 27, 27, 28,
147};
148
149const MatchLength = struct {
150 code: u16,
151 base_scaled: u8, // base - 3, scaled to fit into u8 (0-255), same as lit_len field in Token.
152 base: u16, // 3-258
153 extra_length: u8 = 0,
154 extra_bits: u4,
155};
156
157// match_lengths represents table from rfc (https://datatracker.ietf.org/doc/html/rfc1951#page-12)
158//
159// Extra Extra Extra
160// Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
161// ---- ---- ------ ---- ---- ------- ---- ---- -------
162// 257 0 3 267 1 15,16 277 4 67-82
163// 258 0 4 268 1 17,18 278 4 83-98
164// 259 0 5 269 2 19-22 279 4 99-114
165// 260 0 6 270 2 23-26 280 4 115-130
166// 261 0 7 271 2 27-30 281 5 131-162
167// 262 0 8 272 2 31-34 282 5 163-194
168// 263 0 9 273 3 35-42 283 5 195-226
169// 264 0 10 274 3 43-50 284 5 227-257
170// 265 1 11,12 275 3 51-58 285 0 258
171// 266 1 13,14 276 3 59-66
172//
173pub const length_codes_start = 257;
174
175const match_lengths = [_]MatchLength{
176 .{ .extra_bits = 0, .base_scaled = 0, .base = 3, .code = 257 },
177 .{ .extra_bits = 0, .base_scaled = 1, .base = 4, .code = 258 },
178 .{ .extra_bits = 0, .base_scaled = 2, .base = 5, .code = 259 },
179 .{ .extra_bits = 0, .base_scaled = 3, .base = 6, .code = 260 },
180 .{ .extra_bits = 0, .base_scaled = 4, .base = 7, .code = 261 },
181 .{ .extra_bits = 0, .base_scaled = 5, .base = 8, .code = 262 },
182 .{ .extra_bits = 0, .base_scaled = 6, .base = 9, .code = 263 },
183 .{ .extra_bits = 0, .base_scaled = 7, .base = 10, .code = 264 },
184 .{ .extra_bits = 1, .base_scaled = 8, .base = 11, .code = 265 },
185 .{ .extra_bits = 1, .base_scaled = 10, .base = 13, .code = 266 },
186 .{ .extra_bits = 1, .base_scaled = 12, .base = 15, .code = 267 },
187 .{ .extra_bits = 1, .base_scaled = 14, .base = 17, .code = 268 },
188 .{ .extra_bits = 2, .base_scaled = 16, .base = 19, .code = 269 },
189 .{ .extra_bits = 2, .base_scaled = 20, .base = 23, .code = 270 },
190 .{ .extra_bits = 2, .base_scaled = 24, .base = 27, .code = 271 },
191 .{ .extra_bits = 2, .base_scaled = 28, .base = 31, .code = 272 },
192 .{ .extra_bits = 3, .base_scaled = 32, .base = 35, .code = 273 },
193 .{ .extra_bits = 3, .base_scaled = 40, .base = 43, .code = 274 },
194 .{ .extra_bits = 3, .base_scaled = 48, .base = 51, .code = 275 },
195 .{ .extra_bits = 3, .base_scaled = 56, .base = 59, .code = 276 },
196 .{ .extra_bits = 4, .base_scaled = 64, .base = 67, .code = 277 },
197 .{ .extra_bits = 4, .base_scaled = 80, .base = 83, .code = 278 },
198 .{ .extra_bits = 4, .base_scaled = 96, .base = 99, .code = 279 },
199 .{ .extra_bits = 4, .base_scaled = 112, .base = 115, .code = 280 },
200 .{ .extra_bits = 5, .base_scaled = 128, .base = 131, .code = 281 },
201 .{ .extra_bits = 5, .base_scaled = 160, .base = 163, .code = 282 },
202 .{ .extra_bits = 5, .base_scaled = 192, .base = 195, .code = 283 },
203 .{ .extra_bits = 5, .base_scaled = 224, .base = 227, .code = 284 },
204 .{ .extra_bits = 0, .base_scaled = 255, .base = 258, .code = 285 },
205};
206
207// Used in distanceCode fn to get index in match_distance table for each distance in range 0-32767.
208const match_distances_index = [_]u8{
209 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
210 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
211 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
212 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
213 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
214 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
215 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
216 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
217 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
218 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
219 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
220 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
221 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
222 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
223 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
224 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
225};
226
227const MatchDistance = struct {
228 base_scaled: u16, // base - 1, same as Token dist field
229 base: u16,
230 extra_distance: u16 = 0,
231 code: u8,
232 extra_bits: u4,
233};
234
235// match_distances represents table from rfc (https://datatracker.ietf.org/doc/html/rfc1951#page-12)
236//
237// Extra Extra Extra
238// Code Bits Dist Code Bits Dist Code Bits Distance
239// ---- ---- ---- ---- ---- ------ ---- ---- --------
240// 0 0 1 10 4 33-48 20 9 1025-1536
241// 1 0 2 11 4 49-64 21 9 1537-2048
242// 2 0 3 12 5 65-96 22 10 2049-3072
243// 3 0 4 13 5 97-128 23 10 3073-4096
244// 4 1 5,6 14 6 129-192 24 11 4097-6144
245// 5 1 7,8 15 6 193-256 25 11 6145-8192
246// 6 2 9-12 16 7 257-384 26 12 8193-12288
247// 7 2 13-16 17 7 385-512 27 12 12289-16384
248// 8 3 17-24 18 8 513-768 28 13 16385-24576
249// 9 3 25-32 19 8 769-1024 29 13 24577-32768
250//
251const match_distances = [_]MatchDistance{
252 .{ .extra_bits = 0, .base_scaled = 0x0000, .code = 0, .base = 1 },
253 .{ .extra_bits = 0, .base_scaled = 0x0001, .code = 1, .base = 2 },
254 .{ .extra_bits = 0, .base_scaled = 0x0002, .code = 2, .base = 3 },
255 .{ .extra_bits = 0, .base_scaled = 0x0003, .code = 3, .base = 4 },
256 .{ .extra_bits = 1, .base_scaled = 0x0004, .code = 4, .base = 5 },
257 .{ .extra_bits = 1, .base_scaled = 0x0006, .code = 5, .base = 7 },
258 .{ .extra_bits = 2, .base_scaled = 0x0008, .code = 6, .base = 9 },
259 .{ .extra_bits = 2, .base_scaled = 0x000c, .code = 7, .base = 13 },
260 .{ .extra_bits = 3, .base_scaled = 0x0010, .code = 8, .base = 17 },
261 .{ .extra_bits = 3, .base_scaled = 0x0018, .code = 9, .base = 25 },
262 .{ .extra_bits = 4, .base_scaled = 0x0020, .code = 10, .base = 33 },
263 .{ .extra_bits = 4, .base_scaled = 0x0030, .code = 11, .base = 49 },
264 .{ .extra_bits = 5, .base_scaled = 0x0040, .code = 12, .base = 65 },
265 .{ .extra_bits = 5, .base_scaled = 0x0060, .code = 13, .base = 97 },
266 .{ .extra_bits = 6, .base_scaled = 0x0080, .code = 14, .base = 129 },
267 .{ .extra_bits = 6, .base_scaled = 0x00c0, .code = 15, .base = 193 },
268 .{ .extra_bits = 7, .base_scaled = 0x0100, .code = 16, .base = 257 },
269 .{ .extra_bits = 7, .base_scaled = 0x0180, .code = 17, .base = 385 },
270 .{ .extra_bits = 8, .base_scaled = 0x0200, .code = 18, .base = 513 },
271 .{ .extra_bits = 8, .base_scaled = 0x0300, .code = 19, .base = 769 },
272 .{ .extra_bits = 9, .base_scaled = 0x0400, .code = 20, .base = 1025 },
273 .{ .extra_bits = 9, .base_scaled = 0x0600, .code = 21, .base = 1537 },
274 .{ .extra_bits = 10, .base_scaled = 0x0800, .code = 22, .base = 2049 },
275 .{ .extra_bits = 10, .base_scaled = 0x0c00, .code = 23, .base = 3073 },
276 .{ .extra_bits = 11, .base_scaled = 0x1000, .code = 24, .base = 4097 },
277 .{ .extra_bits = 11, .base_scaled = 0x1800, .code = 25, .base = 6145 },
278 .{ .extra_bits = 12, .base_scaled = 0x2000, .code = 26, .base = 8193 },
279 .{ .extra_bits = 12, .base_scaled = 0x3000, .code = 27, .base = 12289 },
280 .{ .extra_bits = 13, .base_scaled = 0x4000, .code = 28, .base = 16385 },
281 .{ .extra_bits = 13, .base_scaled = 0x6000, .code = 29, .base = 24577 },
282};
283
284test "size" {
285 try expect(@sizeOf(Token) == 4);
286}
287
288// testing table https://datatracker.ietf.org/doc/html/rfc1951#page-12
289test "MatchLength" {
290 var c = Token.initMatch(1, 4).lengthEncoding();
291 try expect(c.code == 258);
292 try expect(c.extra_bits == 0);
293 try expect(c.extra_length == 0);
294
295 c = Token.initMatch(1, 11).lengthEncoding();
296 try expect(c.code == 265);
297 try expect(c.extra_bits == 1);
298 try expect(c.extra_length == 0);
299
300 c = Token.initMatch(1, 12).lengthEncoding();
301 try expect(c.code == 265);
302 try expect(c.extra_bits == 1);
303 try expect(c.extra_length == 1);
304
305 c = Token.initMatch(1, 130).lengthEncoding();
306 try expect(c.code == 280);
307 try expect(c.extra_bits == 4);
308 try expect(c.extra_length == 130 - 115);
309}
310
311test "MatchDistance" {
312 var c = Token.initMatch(1, 4).distanceEncoding();
313 try expect(c.code == 0);
314 try expect(c.extra_bits == 0);
315 try expect(c.extra_distance == 0);
316
317 c = Token.initMatch(192, 4).distanceEncoding();
318 try expect(c.code == 14);
319 try expect(c.extra_bits == 6);
320 try expect(c.extra_distance == 192 - 129);
321}
322
323test "match_lengths" {
324 for (match_lengths, 0..) |ml, i| {
325 try expect(@as(u16, ml.base_scaled) + 3 == ml.base);
326 try expect(i + 257 == ml.code);
327 }
328
329 for (match_distances, 0..) |mo, i| {
330 try expect(mo.base_scaled + 1 == mo.base);
331 try expect(i == mo.code);
332 }
333}
lib/std/compress/flate/token.zig created+286
......@@ -0,0 +1,286 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const min_length = 3;
5pub const max_length = 258;
6
7pub const min_distance = 1;
8pub const max_distance = std.compress.flate.history_len;
9
10pub const codegen_order: [19]u8 = .{
11 16, 17, 18,
12 0, 8, //
13 7, 9,
14 6, 10,
15 5, 11,
16 4, 12,
17 3, 13,
18 2, 14,
19 1, 15,
20};
21
22pub const fixed_lit_codes = fixed_lit[0];
23pub const fixed_lit_bits = fixed_lit[1];
24const fixed_lit = blk: {
25 var codes: [286]u16 = undefined;
26 var bits: [286]u4 = undefined;
27
28 for (0..143 + 1, 0b00110000..0b10111111 + 1) |i, v| {
29 codes[i] = @bitReverse(@as(u8, v));
30 bits[i] = 8;
31 }
32 for (144..255 + 1, 0b110010000..0b111111111 + 1) |i, v| {
33 codes[i] = @bitReverse(@as(u9, v));
34 bits[i] = 9;
35 }
36 for (256..279 + 1, 0b0000000..0b0010111 + 1) |i, v| {
37 codes[i] = @bitReverse(@as(u7, v));
38 bits[i] = 7;
39 }
40 for (280..287 - 2 + 1, 0b11000000..0b11000111 - 2 + 1) |i, v| {
41 codes[i] = @bitReverse(@as(u8, v));
42 bits[i] = 8;
43 }
44 break :blk .{ codes, bits };
45};
46
47pub const fixed_dist_codes = fixed_dist[0];
48pub const fixed_dist_bits = fixed_dist[1];
49const fixed_dist = blk: {
50 var codes: [30]u16 = undefined;
51 const bits: [30]u4 = @splat(5);
52
53 for (0..30) |i| {
54 codes[i] = @bitReverse(@as(u5, i));
55 }
56 break :blk .{ codes, bits };
57};
58
59// All paramters of codes can be derived matchematically, however some are faster to
60// do via lookup table. For ReleaseSmall, we do all mathematically to save space.
61pub const LenCode = if (builtin.mode != .ReleaseSmall) LookupLenCode else ShortLenCode;
62pub const DistCode = if (builtin.mode != .ReleaseSmall) LookupDistCode else ShortDistCode;
63const ShortLenCode = ShortCode(u8, u2, u3, true);
64const ShortDistCode = ShortCode(u15, u1, u4, false);
65/// For length and distance codes, they having this format.
66///
67/// For example, length code 0b1101 (13 or literal 270) has high_bits=0b01 and high_log2=3
68/// and is 1_01_xx (2 extra bits). It is then offsetted by the min length of 3.
69/// ^ bit 4 = 2 + high_log2 - 1
70///
71/// An exception is Length codes, where value 255 is assigned the special zero-bit code 28 or
72/// literal 285.
73fn ShortCode(Value: type, HighBits: type, HighLog2: type, len_special: bool) type {
74 return packed struct(u5) {
75 /// Bits preceding high bit or start if none
76 high_bits: HighBits,
77 /// High bit, 0 means none, otherwise it is at bit `x + high_log2 - 1`
78 high_log2: HighLog2,
79
80 pub fn fromVal(v: Value) @This() {
81 if (len_special and v == 255) return .fromInt(28);
82 const high_bits = @bitSizeOf(HighBits) + 1;
83 const bits = @bitSizeOf(Value) - @clz(v);
84 if (bits <= high_bits) return @bitCast(@as(u5, @intCast(v)));
85 const high = v >> @intCast(bits - high_bits);
86 return .{ .high_bits = @truncate(high), .high_log2 = @intCast(bits - high_bits + 1) };
87 }
88
89 /// `@ctz(return) >= extraBits()`
90 pub fn base(c: @This()) Value {
91 if (len_special and c.toInt() == 28) return 255;
92 if (c.high_log2 <= 1) return @as(u5, @bitCast(c));
93 const high_value = (@as(Value, @intFromBool(c.high_log2 != 0)) << @bitSizeOf(HighBits)) | c.high_bits;
94 const high_start = @as(std.math.Log2Int(Value), c.high_log2 - 1);
95 return @shlExact(high_value, high_start);
96 }
97
98 const max_extra = @bitSizeOf(Value) - (1 + @bitSizeOf(HighLog2));
99 pub fn extraBits(c: @This()) std.math.IntFittingRange(0, max_extra) {
100 if (len_special and c.toInt() == 28) return 0;
101 return @intCast(c.high_log2 -| 1);
102 }
103
104 pub fn toInt(c: @This()) u5 {
105 return @bitCast(c);
106 }
107
108 pub fn fromInt(x: u5) @This() {
109 return @bitCast(x);
110 }
111 };
112}
113
114const LookupLenCode = packed struct(u5) {
115 code: ShortLenCode,
116
117 const code_table = table: {
118 var codes: [256]ShortLenCode = undefined;
119 for (0.., &codes) |v, *c| {
120 c.* = .fromVal(v);
121 }
122 break :table codes;
123 };
124
125 const base_table = table: {
126 var bases: [29]u8 = undefined;
127 for (0.., &bases) |c, *b| {
128 b.* = ShortLenCode.fromInt(c).base();
129 }
130 break :table bases;
131 };
132
133 pub fn fromVal(v: u8) LookupLenCode {
134 return .{ .code = code_table[v] };
135 }
136
137 /// `@ctz(return) >= extraBits()`
138 pub fn base(c: LookupLenCode) u8 {
139 return base_table[c.toInt()];
140 }
141
142 pub fn extraBits(c: LookupLenCode) u3 {
143 return c.code.extraBits();
144 }
145
146 pub fn toInt(c: LookupLenCode) u5 {
147 return @bitCast(c);
148 }
149
150 pub fn fromInt(x: u5) LookupLenCode {
151 return @bitCast(x);
152 }
153};
154
155const LookupDistCode = packed struct(u5) {
156 code: ShortDistCode,
157
158 const base_table = table: {
159 var bases: [30]u15 = undefined;
160 for (0.., &bases) |c, *b| {
161 b.* = ShortDistCode.fromInt(c).base();
162 }
163 break :table bases;
164 };
165
166 pub fn fromVal(v: u15) LookupDistCode {
167 return .{ .code = .fromVal(v) };
168 }
169
170 /// `@ctz(return) >= extraBits()`
171 pub fn base(c: LookupDistCode) u15 {
172 return base_table[c.toInt()];
173 }
174
175 pub fn extraBits(c: LookupDistCode) u4 {
176 return c.code.extraBits();
177 }
178
179 pub fn toInt(c: LookupDistCode) u5 {
180 return @bitCast(c);
181 }
182
183 pub fn fromInt(x: u5) LookupDistCode {
184 return @bitCast(x);
185 }
186};
187
188test LenCode {
189 inline for ([_]type{ ShortLenCode, LookupLenCode }) |Code| {
190 // Check against the RFC 1951 table
191 for (0.., [_]struct {
192 base: u8,
193 extra_bits: u4,
194 }{
195 // zig fmt: off
196 .{ .base = 3 - min_length, .extra_bits = 0 },
197 .{ .base = 4 - min_length, .extra_bits = 0 },
198 .{ .base = 5 - min_length, .extra_bits = 0 },
199 .{ .base = 6 - min_length, .extra_bits = 0 },
200 .{ .base = 7 - min_length, .extra_bits = 0 },
201 .{ .base = 8 - min_length, .extra_bits = 0 },
202 .{ .base = 9 - min_length, .extra_bits = 0 },
203 .{ .base = 10 - min_length, .extra_bits = 0 },
204 .{ .base = 11 - min_length, .extra_bits = 1 },
205 .{ .base = 13 - min_length, .extra_bits = 1 },
206 .{ .base = 15 - min_length, .extra_bits = 1 },
207 .{ .base = 17 - min_length, .extra_bits = 1 },
208 .{ .base = 19 - min_length, .extra_bits = 2 },
209 .{ .base = 23 - min_length, .extra_bits = 2 },
210 .{ .base = 27 - min_length, .extra_bits = 2 },
211 .{ .base = 31 - min_length, .extra_bits = 2 },
212 .{ .base = 35 - min_length, .extra_bits = 3 },
213 .{ .base = 43 - min_length, .extra_bits = 3 },
214 .{ .base = 51 - min_length, .extra_bits = 3 },
215 .{ .base = 59 - min_length, .extra_bits = 3 },
216 .{ .base = 67 - min_length, .extra_bits = 4 },
217 .{ .base = 83 - min_length, .extra_bits = 4 },
218 .{ .base = 99 - min_length, .extra_bits = 4 },
219 .{ .base = 115 - min_length, .extra_bits = 4 },
220 .{ .base = 131 - min_length, .extra_bits = 5 },
221 .{ .base = 163 - min_length, .extra_bits = 5 },
222 .{ .base = 195 - min_length, .extra_bits = 5 },
223 .{ .base = 227 - min_length, .extra_bits = 5 },
224 .{ .base = 258 - min_length, .extra_bits = 0 },
225 }) |code, params| {
226 // zig fmt: on
227 const c: u5 = @intCast(code);
228 try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c))));
229 try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c))));
230 for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits) -
231 @intFromBool(c == 27)) |v|
232 {
233 try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt());
234 }
235 }
236 }
237}
238
239test DistCode {
240 inline for ([_]type{ ShortDistCode, LookupDistCode }) |Code| {
241 for (0.., [_]struct {
242 base: u15,
243 extra_bits: u4,
244 }{
245 // zig fmt: off
246 .{ .base = 1 - min_distance, .extra_bits = 0 },
247 .{ .base = 2 - min_distance, .extra_bits = 0 },
248 .{ .base = 3 - min_distance, .extra_bits = 0 },
249 .{ .base = 4 - min_distance, .extra_bits = 0 },
250 .{ .base = 5 - min_distance, .extra_bits = 1 },
251 .{ .base = 7 - min_distance, .extra_bits = 1 },
252 .{ .base = 9 - min_distance, .extra_bits = 2 },
253 .{ .base = 13 - min_distance, .extra_bits = 2 },
254 .{ .base = 17 - min_distance, .extra_bits = 3 },
255 .{ .base = 25 - min_distance, .extra_bits = 3 },
256 .{ .base = 33 - min_distance, .extra_bits = 4 },
257 .{ .base = 49 - min_distance, .extra_bits = 4 },
258 .{ .base = 65 - min_distance, .extra_bits = 5 },
259 .{ .base = 97 - min_distance, .extra_bits = 5 },
260 .{ .base = 129 - min_distance, .extra_bits = 6 },
261 .{ .base = 193 - min_distance, .extra_bits = 6 },
262 .{ .base = 257 - min_distance, .extra_bits = 7 },
263 .{ .base = 385 - min_distance, .extra_bits = 7 },
264 .{ .base = 513 - min_distance, .extra_bits = 8 },
265 .{ .base = 769 - min_distance, .extra_bits = 8 },
266 .{ .base = 1025 - min_distance, .extra_bits = 9 },
267 .{ .base = 1537 - min_distance, .extra_bits = 9 },
268 .{ .base = 2049 - min_distance, .extra_bits = 10 },
269 .{ .base = 3073 - min_distance, .extra_bits = 10 },
270 .{ .base = 4097 - min_distance, .extra_bits = 11 },
271 .{ .base = 6145 - min_distance, .extra_bits = 11 },
272 .{ .base = 8193 - min_distance, .extra_bits = 12 },
273 .{ .base = 12289 - min_distance, .extra_bits = 12 },
274 .{ .base = 16385 - min_distance, .extra_bits = 13 },
275 .{ .base = 24577 - min_distance, .extra_bits = 13 },
276 }) |code, params| {
277 // zig fmt: on
278 const c: u5 = @intCast(code);
279 try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c))));
280 try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c))));
281 for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits)) |v| {
282 try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt());
283 }
284 }
285 }
286}