authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-03-06 19:10:47-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-11 02:28:19+01:00
log6e5a95bd7c1f6b6bb659908fc7ba70cb5fd1b42c
treebda8d876893b28f29c4d886dd2d5ce4bc581046f
parentc2587582c8af957f032bee1951d923d472c89e2f

implement proper deflate flush semantics

To end a flate stream, `finish` must now be called. `flush` now follows regular semantics and byte-aligns the stream. Byte-aligning the stream is done with empty fixed or store blocks. To implement flush, a variable history length was added and it is tracked if the final bytes of history have been hashed yet.

2 files changed, 209 insertions(+), 110 deletions(-)

lib/std/compress/flate/Compress.zig+208-109
......@@ -2,7 +2,8 @@
22//!
33//! The source of an `error.WriteFailed` is always the backing writer. After an
44//! `error.WriteFailed`, the `.writer` becomes `.failing` and is unrecoverable.
5//! After a `flush`, the writer also becomes `.failing` since the stream has
5//!
6//! After `finish`, the writer also becomes `.failing` since the stream has
67//! been finished. This behavior also applies to `Raw` and `Huffman`.
78
89// Implementation details:
......@@ -43,9 +44,10 @@ const PackedOptionalU15 = packed struct(u16) {
4344 pub const null_bit: PackedOptionalU15 = .{ .value = 0, .is_null = true };
4445};
4546
46/// After `flush` is called, all vtable calls with result in `error.WriteFailed.`
47/// After `finish` is called, all vtable calls with result in `error.WriteFailed`.
4748writer: Writer,
48has_history: bool,
49history_len: u16,
50history_end_unhashed: bool,
4951bit_writer: BitWriter,
5052buffered_tokens: struct {
5153 /// List of `TokenBufferEntryHeader`s and their trailing data.
......@@ -108,7 +110,7 @@ const BitWriter = struct {
108110 b.buffered = @intCast(combined >> (combined_bits - b.buffered_n));
109111 }
110112
111 /// Assserts one byte can be written to `b.otuput` without rebasing.
113 /// Asserts one byte can be written to `b.output` without rebasing.
112114 pub fn byteAlign(b: *BitWriter) void {
113115 b.output.unusedCapacitySlice()[0] = b.buffered;
114116 b.output.advance(@intFromBool(b.buffered_n != 0));
......@@ -116,6 +118,35 @@ const BitWriter = struct {
116118 b.buffered_n = 0;
117119 }
118120
121 /// Byte align using only empty flate blocks
122 pub fn byteAlignBlocks(b: *BitWriter) Writer.Error!void {
123 if (b.buffered_n == 0) return;
124
125 // There are two methods to do this:
126 // 1. A store block (5 or 6 bytes)
127 // 2. Outputting empty 10-bit fixed blocks until aligned
128 //
129 // Fixed blocks advance the bit alignment by two, and so can only used for even numbers
130 // requiring a maximum of four bytes (three blocks = 30 bits) to which is always more
131 // efficient than store blocks.
132 if (b.buffered_n & 1 == 0) {
133 const splat = (8 - @as(u5, b.buffered_n)) >> 1;
134 const bits = splat * 10;
135 // fixed eos code is 0, so the only bits are for the block header
136 const pattern: u32 = BlockHeader.int(.{ .kind = .fixed, .final = false });
137 const splatted = ((pattern << 20) | (pattern << 10) | pattern) >> (30 - bits);
138 try b.write(splatted, bits);
139 } else {
140 try b.write(BlockHeader.int(.{ .kind = .stored, .final = false }), 3);
141 try b.output.rebase(0, 5);
142 b.byteAlign();
143 b.output.writeInt(u16, 0x0000, .little) catch unreachable;
144 b.output.writeInt(u16, 0xffff, .little) catch unreachable;
145 }
146
147 assert(b.buffered_n == 0);
148 }
149
119150 pub fn writeClen(
120151 b: *BitWriter,
121152 hclen: u4,
......@@ -159,9 +190,9 @@ const BitWriter = struct {
159190/// The maximum value is `math.maxInt(u16) - 1` since one token is reserved for end-of-block.
160191const block_tokens: u16 = 1 << 15;
161192const lookup_hash_bits = 15;
162const Hash = u16; // `u[lookup_hash_bits]` is not used due to worse optimization (with LLVM 21)
193const Hash = u16; // `@Int(.unsigned, lookup_hash_bits)` is not used due to worse optimization (with LLVM 21)
163194const seq_bytes = 3; // not intended to be changed
164const Seq = std.meta.Int(.unsigned, seq_bytes * 8);
195const Seq = @Int(.unsigned, seq_bytes * 8);
165196
166197const TokenBufferEntryHeader = packed struct(u16) {
167198 kind: enum(u1) {
......@@ -295,7 +326,8 @@ pub fn init(
295326 .rebase = rebase,
296327 },
297328 },
298 .has_history = false,
329 .history_len = 0,
330 .history_end_unhashed = false,
299331 .bit_writer = .init(output),
300332 .buffered_tokens = .empty,
301333 .lookup = .{
......@@ -314,68 +346,110 @@ fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize
314346 errdefer w.* = .failing;
315347 // There may have not been enough space in the buffer and the write was sent directly here.
316348 // 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.
320349 const data_n = w.buffer.len - w.end;
321350 _ = w.fixedDrain(data, splat) catch {};
322351 assert(w.end == w.buffer.len);
323 try rebaseInner(w, 0, 1, false);
352 try rebaseInner(w, 0, 1, false, false);
324353 return data_n;
325354}
326355
327356fn flush(w: *Writer) Writer.Error!void {
328 defer w.* = .failing;
357 errdefer w.* = .failing;
358 try rebaseInner(w, 0, w.buffer.len - flate.history_len, true, false);
329359 const c: *Compress = @fieldParentPtr("writer", w);
330 try rebaseInner(w, 0, w.buffer.len - flate.history_len, true);
360 try c.bit_writer.byteAlignBlocks();
361}
362
363pub fn finish(c: *Compress) Writer.Error!void {
364 defer c.writer = .failing;
365 try rebaseInner(&c.writer, 0, c.writer.buffer.len - flate.history_len, true, true);
331366 try c.bit_writer.output.rebase(0, 1);
332367 c.bit_writer.byteAlign();
333368 try c.hasher.writeFooter(c.bit_writer.output);
334369}
335370
336371fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
337 return rebaseInner(w, preserve, capacity, false);
372 errdefer w.* = .failing;
373 return rebaseInner(w, preserve, capacity, false, false);
338374}
339375
340376pub const rebase_min_preserve = flate.history_len;
341377pub const rebase_reserved_capacity = (token.max_length + 1) + seq_bytes;
342378
343fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
344 if (!eos) {
379fn rebaseInner(
380 w: *Writer,
381 preserve: usize,
382 capacity: usize,
383 is_flush: bool,
384 is_finish: bool,
385) Writer.Error!void {
386 if (!is_flush) {
345387 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.
349388 } else {
389 // Preverse is not considered for `matching_end`
350390 assert(preserve == 0 and capacity == w.buffer.len - flate.history_len);
351391 }
392 if (is_finish) assert(is_flush);
352393
353394 const c: *Compress = @fieldParentPtr("writer", w);
354395 const buffered = w.buffered();
355396
356 const start = @as(usize, flate.history_len) * @intFromBool(c.has_history);
357 const lit_end: usize = if (!eos)
397 const start: usize = c.history_len;
398 const hashable_len = buffered.len -| (seq_bytes - 1);
399 const matching_end: usize = if (!is_flush)
358400 buffered.len - rebase_reserved_capacity - (preserve -| flate.history_len)
359401 else
360 buffered.len -| (seq_bytes - 1);
402 hashable_len;
361403
362404 var i = start;
363405 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 }
406 var seq: Seq = start_seq: {
407 if (c.history_end_unhashed) {
408 @branchHint(.unlikely);
409
410 assert(i != 0);
411 i -|= seq_bytes - 1;
412 var seq: Seq = mem.readInt(
413 @Int(.unsigned, (seq_bytes - 1) * 8),
414 w.buffer[i..][0 .. seq_bytes - 1],
415 .big,
416 );
417
418 while (i < @min(start, hashable_len)) {
419 seq <<= 8;
420 seq |= buffered[i + (seq_bytes - 1)];
421 c.addHash(i, hash(seq));
422 i += 1;
423 }
424
425 if (i < start) {
426 @branchHint(.unlikely);
427 i = start;
428 assert(i >= hashable_len);
429 assert(i >= matching_end);
430 assert(is_flush);
431 break :start_seq undefined; // Unused
432 }
433
434 c.history_end_unhashed = false;
435 break :start_seq seq;
436 }
437
438 if (i >= hashable_len) {
439 @branchHint(.unlikely);
440 assert(i >= matching_end);
441 assert(is_flush);
442 break :start_seq undefined; // Unused
443 }
377444
378 while (i < lit_end) {
445 break :start_seq mem.readInt(
446 @Int(.unsigned, (seq_bytes - 1) * 8),
447 buffered[i..][0 .. seq_bytes - 1],
448 .big,
449 );
450 };
451
452 while (i < matching_end) {
379453 var match_start = i;
380454 seq <<= 8;
381455 seq |= buffered[i + (seq_bytes - 1)];
......@@ -420,43 +494,50 @@ fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.E
420494
421495 try c.outputBytes(buffered[last_unmatched..match_start]);
422496 try c.outputMatch(@intCast(match.dist), @intCast(match.len - 3));
423
424497 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 }
431498
432 while (true) {
499 while (i < hashable_len) {
433500 seq <<= 8;
434501 seq |= buffered[i + (seq_bytes - 1)];
435 _ = c.addHash(i, hash(seq));
502 c.addHash(i, hash(seq));
436503 i += 1;
437504
438505 match_unadded -= 1;
439506 if (match_unadded == 0) break;
507 } else {
508 @branchHint(.unlikely);
509 assert(is_flush);
510 // `c.history_end_unhashed` is set down below
511 break;
440512 }
441513 assert(i == match_start + match.len);
442514 }
443515
444 if (eos) {
445 i = undefined; // (from match hashing logic)
516 if (is_flush) {
446517 try c.outputBytes(buffered[last_unmatched..]);
447518 c.hasher.update(buffered[start..]);
448 try c.writeBlock(true);
449 return;
450 }
451519
452 try c.outputBytes(buffered[last_unmatched..i]);
453 c.hasher.update(buffered[start..i]);
520 if (is_finish) {
521 try c.writeBlock(true);
522 return; // Other state does not need updated since the writer transitions to `.failing`
523 }
524
525 i = buffered.len;
526 c.history_end_unhashed = i != 0;
527
528 if (c.buffered_tokens.n != 0) {
529 try c.writeBlock(false);
530 }
531 } else {
532 try c.outputBytes(buffered[last_unmatched..i]);
533 c.hasher.update(buffered[start..i]);
534 }
454535
455 const preserved = buffered[i - flate.history_len ..];
456 assert(preserved.len > @max(rebase_min_preserve, preserve));
536 c.history_len = @min(i, flate.history_len);
537 const preserved = buffered[i - c.history_len ..];
538 if (!is_flush) assert(preserved.len >= @max(rebase_min_preserve, preserve));
457539 @memmove(w.buffer[0..preserved.len], preserved);
458540 w.end = preserved.len;
459 c.has_history = true;
460541}
461542
462543fn addHash(c: *Compress, i: usize, h: Hash) void {
......@@ -499,7 +580,7 @@ fn betterMatchLen(old: u16, prev: []const u8, bytes: []const u8) u16 {
499580 assert(bytes.len >= token.min_length);
500581
501582 var i: u16 = 0;
502 const Block = std.meta.Int(.unsigned, @min(math.divCeil(
583 const Block = @Int(.unsigned, @min(math.divCeil(
503584 comptime_int,
504585 math.ceilPowerOfTwoAssert(usize, @bitSizeOf(usize)),
505586 8,
......@@ -798,7 +879,6 @@ test buildClen {
798879
799880fn writeBlock(c: *Compress, eos: bool) Writer.Error!void {
800881 const toks = &c.buffered_tokens;
801 if (!eos) assert(toks.n == block_tokens);
802882 assert(toks.lit_freqs[256] == 0);
803883 toks.lit_freqs[256] = 1;
804884
......@@ -1438,20 +1518,8 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith
14381518 .chain = chain,
14391519 });
14401520
1441 // It is ensured that more bytes are not written then this to ensure this run
1442 // does not take too long and that `flate_buf` does not run out of space.
1443 const flate_buf_blocks = flate_buf.len / block_tokens;
1444 // Allow a max overhead of 64 bytes per block since the implementation does not gaurauntee it
1445 // writes store blocks when optimal. This comes from taking less than 32 bytes to write an
1446 // optimal dynamic block header of mostly bitlen 8 codes and the end of block literal plus
1447 // `(65536 / 256) / 8`, which is is the maximum number of extra bytes from bitlen 9 codes. An
1448 // extra 32 bytes is reserved on top of that for container headers and footers.
1449 const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32);
1450
1521 var max_output: usize = 32; // Headers / footer
14511522 while (!smith.eosWeightedSimple(7, 1)) {
1452 const max_bytes = max_size -| expected_size;
1453 if (max_bytes == 0) break;
1454
14551523 const buffered = deflate_w.writer.buffered();
14561524 // Required for repeating patterns and since writing from `buffered` is illegal
14571525 var copy_buf: [512]u8 = undefined;
......@@ -1459,13 +1527,13 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith
14591527 const bytes = bytes: switch (smith.valueRangeAtMost(
14601528 u2,
14611529 @intFromBool(buffered.len == 0),
1462 2,
1530 3,
14631531 )) {
14641532 0 => { // Copy
14651533 const start = smith.valueRangeLessThan(u32, 0, @intCast(buffered.len));
14661534 // Reuse the implementation's history; otherwise, our own would need maintained.
14671535 const from = buffered[start..];
1468 const len = smith.valueRangeAtMost(u16, 1, @min(copy_buf.len, max_bytes));
1536 const len = smith.valueRangeAtMost(u16, 1, copy_buf.len);
14691537
14701538 const history_bytes = from[0..@min(from.len, len)];
14711539 @memcpy(copy_buf[0..history_bytes.len], history_bytes);
......@@ -1485,7 +1553,7 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith
14851553 .value(FreqBufIndex, .random, 1),
14861554 })
14871555 ];
1488 const len = smith.valueRangeAtMost(u32, 1, @min(fbuf.len, max_bytes));
1556 const len = smith.valueRangeAtMost(u32, 1, fbuf.len);
14891557 const off = smith.valueRangeAtMost(u32, 0, @intCast(fbuf.len - len));
14901558 break :bytes fbuf[off..][0..len];
14911559 },
......@@ -1493,25 +1561,42 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith
14931561 const rebaseable = bufsize - rebase_reserved_capacity;
14941562 const capacity = smith.valueRangeAtMost(u32, 1, rebaseable - rebase_min_preserve);
14951563 const preserve = smith.valueRangeAtMost(u32, 0, rebaseable - capacity);
1496 try deflate_w.writer.rebase(preserve, capacity);
1564 const failed = deflate_w.writer.rebase(preserve, capacity);
1565 if (flate_w.buffered().len > max_output) return error.OverheadTooLarge;
1566 failed catch return; // Wrote too much data and ran out of space
1567 continue;
1568 },
1569 3 => { // Flush
1570 max_output += 8; // Alignment data
1571 const failed = deflate_w.writer.flush();
1572 if (flate_w.buffered().len > max_output) return error.OverheadTooLarge;
1573 failed catch return; // Wrote too much data and ran out of space
14971574 continue;
14981575 },
1499 else => unreachable,
15001576 };
15011577
1502 assert(bytes.len <= max_bytes);
1503 try deflate_w.writer.writeAll(bytes);
1578 // An overhead of 64 bytes is given for each block since the implementation does not
1579 // gaurauntee it writes store blocks when optimal. This comes from taking less than 32
1580 // bytes to write an optimal dynamic block header of mostly bitlen 8 codes and the end
1581 // of block literal plus `(65536 / 256) / 8`, which is is the maximum number of extra
1582 // bytes from bitlen 9 codes.
1583 max_output += bytes.len + ((bytes.len + flate_buf.len - 1) / block_tokens) * 64;
1584 const failed = deflate_w.writer.writeAll(bytes);
1585 if (flate_w.buffered().len > max_output) return error.OverheadTooLarge;
1586 failed catch return; // Wrote too much data and ran out of space
15041587 expected_hash.update(bytes);
15051588 expected_size += @intCast(bytes.len);
15061589 }
15071590
1508 try deflate_w.writer.flush();
1591 const failed = deflate_w.finish();
1592 if (flate_w.buffered().len > max_output) return error.OverheadTooLarge;
1593 failed catch return; // Wrote too much data and ran out of space
15091594 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
15101595}
15111596
15121597/// Does not compress data
15131598pub const Raw = struct {
1514 /// After `flush` is called, all vtable calls with result in `error.WriteFailed.`
1599 /// After `finish` is called, all vtable calls with result in `error.WriteFailed`.
15151600 writer: Writer,
15161601 output: *Writer,
15171602 hasher: flate.Container.Hasher,
......@@ -1654,8 +1739,12 @@ pub const Raw = struct {
16541739 }
16551740
16561741 fn flush(w: *Writer) Writer.Error!void {
1657 defer w.* = .failing;
1658 try Raw.rebaseInner(w, 0, w.buffer.len, true);
1742 errdefer w.* = .failing;
1743 try Raw.rebaseInner(w, 0, w.buffer.len, false);
1744 }
1745
1746 fn finish(r: *Raw) Writer.Error!void {
1747 try Raw.rebaseInner(&r.writer, 0, r.writer.buffer.len, true);
16591748 }
16601749
16611750 fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
......@@ -1899,19 +1988,21 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith)
18991988 const Op = packed struct {
19001989 drain: bool = false,
19011990 add_vec: bool = false,
1902 rebase: bool = false,
1991 rebase: enum(u2) { none, rebase, flush } = .none,
19031992
19041993 pub const drain_only: @This() = .{ .drain = true };
19051994 pub const add_vec_only: @This() = .{ .add_vec = true };
19061995 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
1907 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
1996 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = .rebase };
1997 pub const drain_and_flush: @This() = .{ .drain = true, .rebase = .flush };
19081998 };
19091999
19102000 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
19112001 var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
1912 .value(Op, .add_vec_only, 6),
2002 .value(Op, .add_vec_only, 5),
19132003 .value(Op, .add_vec_and_drain, 1),
19142004 .value(Op, .drain_and_rebase, 1),
2005 .value(Op, .drain_and_flush, 1),
19152006 }) else .drain_only;
19162007
19172008 if (op.add_vec) {
......@@ -1965,16 +2056,20 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith)
19652056 vecs_n = 0;
19662057 }
19672058
1968 if (op.rebase) {
1969 const capacity = smith.valueRangeAtMost(u32, 0, raw_buf_len);
1970 const preserve = smith.valueRangeAtMost(u32, 0, raw_buf_len - capacity);
1971 try raw.writer.rebase(preserve, capacity);
2059 switch (op.rebase) {
2060 .none => {},
2061 .rebase => {
2062 const capacity = smith.valueRangeAtMost(u32, 0, raw_buf_len);
2063 const preserve = smith.valueRangeAtMost(u32, 0, raw_buf_len - capacity);
2064 try raw.writer.rebase(preserve, capacity);
2065 },
2066 .flush => try raw.writer.flush(),
19722067 }
19732068
19742069 if (is_eos) break;
19752070 }
19762071
1977 try raw.writer.flush();
2072 try raw.finish();
19782073 try output.writer.flush();
19792074
19802075 try std.testing.expectEqual(.end, output.state);
......@@ -1997,6 +2092,7 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith)
19972092
19982093/// Only performs huffman compression on data, does no matching.
19992094pub const Huffman = struct {
2095 /// After `finish` is called, all vtable calls with result in `error.WriteFailed`.
20002096 writer: Writer,
20012097 bit_writer: BitWriter,
20022098 hasher: flate.Container.Hasher,
......@@ -2026,12 +2122,6 @@ pub const Huffman = struct {
20262122 }
20272123
20282124 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
2029 {
2030 //std.debug.print("drain {} (buffered)", .{w.buffered().len});
2031 //for (data) |d| std.debug.print("\n\t+ {}", .{d.len});
2032 //std.debug.print(" x {}\n\n", .{splat});
2033 }
2034
20352125 const h: *Huffman = @fieldParentPtr("writer", w);
20362126 const min_block = @min(w.buffer.len, max_tokens);
20372127 const pattern = data[data.len - 1];
......@@ -2238,9 +2328,15 @@ pub const Huffman = struct {
22382328 }
22392329
22402330 fn flush(w: *Writer) Writer.Error!void {
2241 defer w.* = .failing;
2331 errdefer w.* = .failing;
22422332 const h: *Huffman = @fieldParentPtr("writer", w);
2243 try Huffman.rebaseInner(w, 0, w.buffer.len, true);
2333 try Huffman.rebaseInner(w, 0, w.buffer.len, false);
2334 try h.bit_writer.byteAlignBlocks();
2335 }
2336
2337 fn finish(h: *Huffman) Writer.Error!void {
2338 defer h.writer = .failing;
2339 try Huffman.rebaseInner(&h.writer, 0, h.writer.buffer.len, true);
22442340 try h.bit_writer.output.rebase(0, 1);
22452341 h.bit_writer.byteAlign();
22462342 try h.hasher.writeFooter(h.bit_writer.output);
......@@ -2359,9 +2455,6 @@ pub const Huffman = struct {
23592455 break :n stored_align_bits + @as(u32, 32) + @as(u32, bytes) * 8;
23602456 };
23612457
2362 //std.debug.print("@ {}{{{}}} ", .{ h.bit_writer.output.end, h.bit_writer.buffered_n });
2363 //std.debug.print("#{} -> s {} f {} d {}\n", .{ bytes, stored_bitsize, fixed_bitsize, dynamic_bitsize });
2364
23652458 if (stored_bitsize <= @min(dynamic_bitsize, fixed_bitsize)) {
23662459 try h.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3);
23672460 try h.bit_writer.output.rebase(0, 5);
......@@ -2434,19 +2527,21 @@ fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith)
24342527 const Op = packed struct {
24352528 drain: bool = false,
24362529 add_vec: bool = false,
2437 rebase: bool = false,
2530 rebase: enum(u2) { none, rebase, flush } = .none,
24382531
24392532 pub const drain_only: @This() = .{ .drain = true };
24402533 pub const add_vec_only: @This() = .{ .add_vec = true };
24412534 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
2442 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
2535 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = .rebase };
2536 pub const drain_and_flush: @This() = .{ .drain = true, .rebase = .flush };
24432537 };
24442538
24452539 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
24462540 var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
2447 .value(Op, .add_vec_only, 6),
2541 .value(Op, .add_vec_only, 5),
24482542 .value(Op, .add_vec_and_drain, 1),
24492543 .value(Op, .drain_and_rebase, 1),
2544 .value(Op, .drain_and_flush, 1),
24502545 }) else .drain_only;
24512546
24522547 if (op.add_vec) {
......@@ -2517,7 +2612,7 @@ fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith)
25172612 vecs_n = 0;
25182613 }
25192614
2520 if (op.rebase) {
2615 if (op.rebase != .none) {
25212616 const capacity = smith.valueRangeAtMost(u32, 0, h_buf_len);
25222617 const preserve = smith.valueRangeAtMost(u32, 0, h_buf_len - capacity);
25232618
......@@ -2525,9 +2620,14 @@ fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith)
25252620 h.writer.buffered().len,
25262621 flate_w.buffered().len,
25272622 false,
2528 );
2529 h.writer.rebase(preserve, capacity) catch
2530 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2623 ) + @as(usize, 8) * @intFromBool(op.rebase == .flush); // Overhead from byte alignment
2624 switch (op.rebase) {
2625 .none => unreachable,
2626 .rebase => h.writer.rebase(preserve, capacity) catch
2627 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {},
2628 .flush => h.writer.flush() catch
2629 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {},
2630 }
25312631 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
25322632 }
25332633
......@@ -2539,8 +2639,7 @@ fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith)
25392639 flate_w.buffered().len,
25402640 true,
25412641 );
2542 h.writer.flush() catch
2543 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2642 h.finish() catch return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
25442643 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
25452644
25462645 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
src/Package/Fetch.zig+1-1
......@@ -447,7 +447,7 @@ pub const JobQueue = struct {
447447
448448 // intentionally omitting the pointless trailer
449449 //try archiver.finish();
450 compress.writer.flush() catch |err| switch (err) {
450 compress.finish() catch |err| switch (err) {
451451 error.WriteFailed => return file_writer.err.?,
452452 };
453453 try file_writer.flush();