authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-28 16:46:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-31 22:10:11-07:00
loge73ca2444e1d7a42266af5496f24ec122fd18f2c
tree7364c255360314267c80dca10b914155575a8444
parent7bf91d705c241609eb9d459d7556b2fc2afee7a1

std.compress.flate.Decompress: implement peekBitsEnding and writeMatch


2 files changed, 61 insertions(+), 37 deletions(-)

lib/std/compress/flate.zig+2-2
...@@ -11,8 +11,8 @@ pub const history_len = 32768;...@@ -11,8 +11,8 @@ pub const history_len = 32768;
11/// of LZ77 and Huffman coding.11/// of LZ77 and Huffman coding.
12pub const Compress = @import("flate/Compress.zig");12pub const Compress = @import("flate/Compress.zig");
1313
14/// Inflate is the decoding process that takes a Deflate bitstream for14/// Inflate is the decoding process that consumes a Deflate bitstream and
15/// decompression and correctly produces the original full-size data or file.15/// produces the original full-size data.
16pub const Decompress = @import("flate/Decompress.zig");16pub const Decompress = @import("flate/Decompress.zig");
1717
18/// Compression without Lempel-Ziv match searching. Faster compression, less18/// Compression without Lempel-Ziv match searching. Faster compression, less
lib/std/compress/flate/Decompress.zig+59-35
...@@ -97,7 +97,7 @@ fn decodeLength(self: *Decompress, code: u8) !u16 {...@@ -97,7 +97,7 @@ fn decodeLength(self: *Decompress, code: u8) !u16 {
97 return if (ml.extra_bits == 0) // 0 - 5 extra bits97 return if (ml.extra_bits == 0) // 0 - 5 extra bits
98 ml.base98 ml.base
99 else99 else
100 ml.base + try self.takeNBitsBuffered(ml.extra_bits);100 ml.base + try self.takeBitsRuntime(ml.extra_bits);
101}101}
102102
103fn decodeDistance(self: *Decompress, code: u8) !u16 {103fn decodeDistance(self: *Decompress, code: u8) !u16 {
...@@ -106,7 +106,7 @@ fn decodeDistance(self: *Decompress, code: u8) !u16 {...@@ -106,7 +106,7 @@ fn decodeDistance(self: *Decompress, code: u8) !u16 {
106 return if (md.extra_bits == 0) // 0 - 13 extra bits106 return if (md.extra_bits == 0) // 0 - 13 extra bits
107 md.base107 md.base
108 else108 else
109 md.base + try self.takeNBitsBuffered(md.extra_bits);109 md.base + try self.takeBitsRuntime(md.extra_bits);
110}110}
111111
112// Decode code length symbol to code length. Writes decoded length into112// Decode code length symbol to code length. Writes decoded length into
...@@ -293,7 +293,8 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S...@@ -293,7 +293,8 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S
293 // Length code is followed by 5 bits of distance code.293 // Length code is followed by 5 bits of distance code.
294 const length = try d.decodeLength(@intCast(code - 257));294 const length = try d.decodeLength(@intCast(code - 257));
295 const distance = try d.decodeDistance(@bitReverse(try d.takeBits(u5)));295 const distance = try d.decodeDistance(@bitReverse(try d.takeBits(u5)));
296 remaining = try writeMatch(w, length, distance, remaining);296 try writeMatch(w, length, distance);
297 remaining -= length;
297 },298 },
298 else => return error.InvalidCode,299 else => return error.InvalidCode,
299 }300 }
...@@ -317,7 +318,8 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S...@@ -317,7 +318,8 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S
317 const length = try d.decodeLength(sym.symbol);318 const length = try d.decodeLength(sym.symbol);
318 const dsm = try d.decodeSymbol(&d.dst_dec);319 const dsm = try d.decodeSymbol(&d.dst_dec);
319 const distance = try d.decodeDistance(dsm.symbol);320 const distance = try d.decodeDistance(dsm.symbol);
320 remaining = try writeMatch(w, length, distance, remaining);321 try writeMatch(w, length, distance);
322 remaining -= length;
321 },323 },
322 .end_of_block => {324 .end_of_block => {
323 d.state = if (d.final_block) .protocol_footer else .block_header;325 d.state = if (d.final_block) .protocol_footer else .block_header;
...@@ -350,12 +352,19 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S...@@ -350,12 +352,19 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S
350352
351/// Write match (back-reference to the same data slice) starting at `distance`353/// Write match (back-reference to the same data slice) starting at `distance`
352/// back from current write position, and `length` of bytes.354/// back from current write position, and `length` of bytes.
353fn writeMatch(w: *Writer, length: u16, distance: u16, remaining: usize) !usize {355fn writeMatch(w: *Writer, length: u16, distance: u16) !void {
354 _ = w;356 if (w.end < length) return error.InvalidMatch;
355 _ = length;357 if (length < Token.base_length) return error.InvalidMatch;
356 _ = distance;358 if (length > Token.max_length) return error.InvalidMatch;
357 _ = remaining;359 if (distance < Token.min_distance) return error.InvalidMatch;
358 @panic("TODO");360 if (distance > Token.max_distance) return error.InvalidMatch;
361
362 // This is not a @memmove; it intentionally repeats patterns caused by
363 // iterating one byte at a time.
364 const dest = try w.writableSlicePreserve(flate.history_len, length);
365 const end = dest.ptr - w.buffer.ptr;
366 const src = w.buffer[end - distance ..][0..length];
367 for (dest, src) |*d, s| d.* = s;
359}368}
360369
361fn takeBits(d: *Decompress, comptime T: type) !T {370fn takeBits(d: *Decompress, comptime T: type) !T {
...@@ -417,35 +426,48 @@ fn takeBitsEnding(d: *Decompress, comptime T: type) !T {...@@ -417,35 +426,48 @@ fn takeBitsEnding(d: *Decompress, comptime T: type) !T {
417 };426 };
418}427}
419428
420fn peekBits(d: *Decompress, comptime T: type) !T {429fn peekBits(d: *Decompress, comptime U: type) !U {
421 const U = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } });
422 const remaining_bits = d.remaining_bits;430 const remaining_bits = d.remaining_bits;
423 const next_bits = d.next_bits;431 const next_bits = d.next_bits;
424 if (remaining_bits >= @bitSizeOf(T)) {432 if (remaining_bits >= @bitSizeOf(U)) return @truncate(next_bits);
425 const u: U = @truncate(next_bits);
426 return switch (@typeInfo(T)) {
427 .int => u,
428 .@"enum" => @enumFromInt(u),
429 else => @bitCast(u),
430 };
431 }
432 const in = d.input;433 const in = d.input;
433 const next_int = in.peekInt(usize, .little) catch |err| switch (err) {434 const next_int = in.peekInt(usize, .little) catch |err| switch (err) {
434 error.ReadFailed => return error.ReadFailed,435 error.ReadFailed => return error.ReadFailed,
435 error.EndOfStream => return peekBitsEnding(d, T),436 error.EndOfStream => return peekBitsEnding(d, U),
436 };
437 const needed_bits = @bitSizeOf(T) - remaining_bits;
438 const u: U = @intCast(((next_int & ((@as(usize, 1) << needed_bits) - 1)) << remaining_bits) | next_bits);
439 return switch (@typeInfo(T)) {
440 .int => u,
441 .@"enum" => @enumFromInt(u),
442 else => @bitCast(u),
443 };437 };
438 const needed_bits = @bitSizeOf(U) - remaining_bits;
439 return @intCast(((next_int & ((@as(usize, 1) << needed_bits) - 1)) << remaining_bits) | next_bits);
444}440}
445441
446fn peekBitsEnding(d: *Decompress, comptime T: type) !T {442fn peekBitsEnding(d: *Decompress, comptime U: type) !U {
447 _ = d;443 const remaining_bits = d.remaining_bits;
448 @panic("TODO");444 const next_bits = d.next_bits;
445 const in = d.input;
446 var u: U = 0;
447 var remaining_needed_bits = @bitSizeOf(U) - remaining_bits;
448 var peek_len: usize = 0;
449 while (@bitSizeOf(U) >= 8 and remaining_needed_bits >= 8) {
450 peek_len += 1;
451 const byte = try specialPeek(in, next_bits, peek_len);
452 u = (u << 8) | byte;
453 remaining_needed_bits -= 8;
454 }
455 if (remaining_needed_bits != 0) {
456 peek_len += 1;
457 const byte = try specialPeek(in, next_bits, peek_len);
458 u = @intCast((@as(usize, u) << remaining_needed_bits) | (byte & ((@as(usize, 1) << remaining_needed_bits) - 1)));
459 }
460 return @intCast((@as(usize, u) << remaining_bits) | next_bits);
461}
462
463/// If there is any unconsumed data, handles EndOfStream by pretending there
464/// are zeroes afterwards.
465fn specialPeek(in: *Reader, next_bits: usize, n: usize) Reader.Error!u8 {
466 const peeked = in.peek(n) catch |err| switch (err) {
467 error.ReadFailed => return error.ReadFailed,
468 error.EndOfStream => if (next_bits == 0 and n == 0) return error.EndOfStream else return 0,
469 };
470 return peeked[n - 1];
449}471}
450472
451fn tossBits(d: *Decompress, n: u6) !void {473fn tossBits(d: *Decompress, n: u6) !void {
...@@ -472,10 +494,12 @@ fn tossBitsEnding(d: *Decompress, n: u6) !void {...@@ -472,10 +494,12 @@ fn tossBitsEnding(d: *Decompress, n: u6) !void {
472 @panic("TODO");494 @panic("TODO");
473}495}
474496
475fn takeNBitsBuffered(d: *Decompress, n: u4) !u16 {497fn takeBitsRuntime(d: *Decompress, n: u4) !u16 {
476 _ = d;498 const x = try peekBits(d, u16);
477 _ = n;499 const mask: u16 = (@as(u16, 1) << n) - 1;
478 @panic("TODO");500 const u: u16 = @as(u16, @truncate(x)) & mask;
501 try tossBits(d, n);
502 return u;
479}503}
480504
481fn alignBitsToByte(d: *Decompress) void {505fn alignBitsToByte(d: *Decompress) void {