| ... | @@ -229,11 +229,11 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz | ... | @@ -229,11 +229,11 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz |
| 229 | } | 229 | } |
| 230 | } | 230 | } |
| 231 | | 231 | |
| 232 | fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol { | 232 | fn decodeSymbol(self: *Decompress, decoder: anytype) !u16 { |
| 233 | // Maximum code len is 15 bits. | 233 | // Maximum code len is 15 bits. |
| 234 | const sym = try decoder.find(@bitReverse(try self.peekIntBitsShort(u15))); | 234 | const sym = try decoder.find(try self.peekIntBitsShort(u15)); |
| 235 | try self.tossBitsShort(sym.code_bits); | 235 | try self.tossBitsShort(sym.code_bits); |
| 236 | return sym; | 236 | return sym.value; |
| 237 | } | 237 | } |
| 238 | | 238 | |
| 239 | fn streamDirect(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { | 239 | fn streamDirect(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { |
| ... | @@ -348,10 +348,10 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -348,10 +348,10 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 348 | var dec_lens: [286 + 30]u4 = @splat(0); | 348 | var dec_lens: [286 + 30]u4 = @splat(0); |
| 349 | var pos: usize = 0; | 349 | var pos: usize = 0; |
| 350 | while (pos < hlit + hdist) { | 350 | while (pos < hlit + hdist) { |
| 351 | const peeked = @bitReverse(try d.peekIntBitsShort(u7)); | 351 | const peeked = try d.peekIntBitsShort(u7); |
| 352 | const sym = try cl_dec.find(peeked); | 352 | const sym = try cl_dec.find(peeked); |
| 353 | try d.tossBitsShort(sym.code_bits); | 353 | try d.tossBitsShort(sym.code_bits); |
| 354 | pos += try d.dynamicCodeLength(sym.symbol, &dec_lens, pos); | 354 | pos += try d.dynamicCodeLength(sym.value, &dec_lens, pos); |
| 355 | } | 355 | } |
| 356 | if (pos > hlit + hdist) { | 356 | if (pos > hlit + hdist) { |
| 357 | return error.InvalidDynamicBlockHeader; | 357 | return error.InvalidDynamicBlockHeader; |
| ... | @@ -383,35 +383,34 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -383,35 +383,34 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 383 | w.advance(n); | 383 | w.advance(n); |
| 384 | return @intFromEnum(limit) - remaining + n; | 384 | return @intFromEnum(limit) - remaining + n; |
| 385 | }, | 385 | }, |
| 386 | .fixed_block => { | 386 | .fixed_block => while (true) { |
| 387 | while (remaining > 0) { | 387 | // Consume bytes |
| 388 | const code = try d.readFixedCode(); | 388 | const sym = try d.readFixedCode(); |
| 389 | switch (code) { | 389 | |
| 390 | 0...255 => { | 390 | if (sym >= 256) { |
| 391 | if (remaining != 0) { | 391 | @branchHint(.unlikely); |
| 392 | @branchHint(.likely); | 392 | |
| 393 | try w.writeBytePreserve(flate.history_len, @intCast(code)); | 393 | if (sym == 256) { |
| 394 | remaining -= 1; | 394 | @branchHint(.unlikely); |
| 395 | } else { | 395 | // End |
| 396 | d.state = .{ .fixed_block_literal = @intCast(code) }; | 396 | d.state = if (d.final_block) .protocol_footer else .block_header; |
| 397 | return @intFromEnum(limit) - remaining; | 397 | continue :sw d.state; |
| 398 | } | | |
| 399 | }, | | |
| 400 | 256 => { | | |
| 401 | d.state = if (d.final_block) .protocol_footer else .block_header; | | |
| 402 | return @intFromEnum(limit) - remaining; | | |
| 403 | }, | | |
| 404 | 257...285 => { | | |
| 405 | // Handles fixed block non literal (length) code. | | |
| 406 | // Length code is followed by 5 bits of distance code. | | |
| 407 | const length = try d.decodeLength(@intCast(code - 257)); | | |
| 408 | continue :sw .{ .fixed_block_match = length }; | | |
| 409 | }, | | |
| 410 | else => return error.InvalidCode, | | |
| 411 | } | 398 | } |
| | 399 | |
| | 400 | // Match |
| | 401 | const length = try d.decodeLength(@intCast(sym - 257)); |
| | 402 | continue :sw .{ .fixed_block_match = length }; |
| | 403 | } |
| | 404 | |
| | 405 | const byte: u8 = @intCast(sym); |
| | 406 | if (remaining != 0) { |
| | 407 | @branchHint(.likely); |
| | 408 | remaining -= 1; |
| | 409 | try w.writeBytePreserve(flate.history_len, byte); |
| | 410 | } else { |
| | 411 | d.state = .{ .fixed_block_literal = byte }; |
| | 412 | return @intFromEnum(limit) - remaining; |
| 412 | } | 413 | } |
| 413 | d.state = .fixed_block; | | |
| 414 | return @intFromEnum(limit) - remaining; | | |
| 415 | }, | 414 | }, |
| 416 | .fixed_block_literal => |symbol| { | 415 | .fixed_block_literal => |symbol| { |
| 417 | assert(remaining != 0); | 416 | assert(remaining != 0); |
| ... | @@ -431,32 +430,35 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -431,32 +430,35 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 431 | return @intFromEnum(limit) - remaining; | 430 | return @intFromEnum(limit) - remaining; |
| 432 | } | 431 | } |
| 433 | }, | 432 | }, |
| 434 | .dynamic_block => { | 433 | // In larger archives most blocks are usually dynamic, so |
| 435 | // In larger archives most blocks are usually dynamic, so | 434 | // decompression performance depends on this logic. |
| 436 | // decompression performance depends on this logic. | 435 | .dynamic_block => while (true) { |
| 437 | var sym = try d.decodeSymbol(&d.lit_dec); | 436 | // Consume bytes |
| 438 | sym: switch (sym.kind) { | 437 | const sym = try d.decodeSymbol(&d.lit_dec); |
| 439 | .literal => { | 438 | |
| 440 | if (remaining != 0) { | 439 | if (sym >= 256) { |
| 441 | @branchHint(.likely); | 440 | @branchHint(.unlikely); |
| 442 | remaining -= 1; | 441 | |
| 443 | try w.writeBytePreserve(flate.history_len, sym.symbol); | 442 | if (sym == 256) { |
| 444 | sym = try d.decodeSymbol(&d.lit_dec); | 443 | @branchHint(.unlikely); |
| 445 | continue :sym sym.kind; | 444 | // End |
| 446 | } else { | | |
| 447 | d.state = .{ .dynamic_block_literal = sym.symbol }; | | |
| 448 | return @intFromEnum(limit) - remaining; | | |
| 449 | } | | |
| 450 | }, | | |
| 451 | .match => { | | |
| 452 | // Decode match backreference <length, distance> | | |
| 453 | const length = try d.decodeLength(@intCast(sym.symbol)); | | |
| 454 | continue :sw .{ .dynamic_block_match = length }; | | |
| 455 | }, | | |
| 456 | .end_of_block => { | | |
| 457 | d.state = if (d.final_block) .protocol_footer else .block_header; | 445 | d.state = if (d.final_block) .protocol_footer else .block_header; |
| 458 | continue :sw d.state; | 446 | continue :sw d.state; |
| 459 | }, | 447 | } |
| | 448 | |
| | 449 | // Match |
| | 450 | const length = try d.decodeLength(@intCast(sym - 257)); |
| | 451 | continue :sw .{ .dynamic_block_match = length }; |
| | 452 | } |
| | 453 | |
| | 454 | const byte: u8 = @intCast(sym); |
| | 455 | if (remaining != 0) { |
| | 456 | @branchHint(.likely); |
| | 457 | remaining -= 1; |
| | 458 | try w.writeBytePreserve(flate.history_len, byte); |
| | 459 | } else { |
| | 460 | d.state = .{ .dynamic_block_literal = byte }; |
| | 461 | return @intFromEnum(limit) - remaining; |
| 460 | } | 462 | } |
| 461 | }, | 463 | }, |
| 462 | .dynamic_block_literal => |symbol| { | 464 | .dynamic_block_literal => |symbol| { |
| ... | @@ -470,7 +472,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -470,7 +472,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 470 | @branchHint(.likely); | 472 | @branchHint(.likely); |
| 471 | remaining -= length; | 473 | remaining -= length; |
| 472 | const dsm = try d.decodeSymbol(&d.dst_dec); | 474 | const dsm = try d.decodeSymbol(&d.dst_dec); |
| 473 | const distance = try d.decodeDistance(@intCast(dsm.symbol)); | 475 | const distance = try d.decodeDistance(@intCast(dsm)); |
| 474 | try writeMatch(w, length, distance); | 476 | try writeMatch(w, length, distance); |
| 475 | continue :sw .dynamic_block; | 477 | continue :sw .dynamic_block; |
| 476 | } else { | 478 | } else { |
| ... | @@ -501,17 +503,25 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -501,17 +503,25 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 501 | /// back from current write position, and `length` of bytes. | 503 | /// back from current write position, and `length` of bytes. |
| 502 | fn writeMatch(w: *Writer, length: u16, distance: u16) !void { | 504 | fn writeMatch(w: *Writer, length: u16, distance: u16) !void { |
| 503 | if (w.end < distance) return error.InvalidMatch; | 505 | if (w.end < distance) return error.InvalidMatch; |
| 504 | if (length < token.min_length) return error.InvalidMatch; | 506 | assert(length >= token.min_length); |
| 505 | if (length > token.max_length) return error.InvalidMatch; | 507 | assert(length <= token.max_length); |
| 506 | if (distance < token.min_distance) return error.InvalidMatch; | 508 | assert(distance >= token.min_distance); |
| 507 | if (distance > token.max_distance) return error.InvalidMatch; | 509 | assert(distance <= token.max_distance); |
| 508 | | 510 | |
| 509 | // This is not a @memmove; it intentionally repeats patterns caused by | 511 | // This is not a @memmove; it intentionally repeats patterns caused by |
| 510 | // iterating one byte at a time. | 512 | // iterating one byte at a time. |
| 511 | const dest = try w.writableSlicePreserve(flate.history_len, length); | 513 | const dest = try w.writableSlicePreserve(flate.history_len, length); |
| 512 | const end = dest.ptr - w.buffer.ptr; | 514 | const end = dest.ptr - w.buffer.ptr; |
| 513 | const src = w.buffer[end - distance ..][0..length]; | 515 | const src = w.buffer[end - distance ..][0..length]; |
| 514 | for (dest, src) |*d, s| d.* = s; | 516 | if (distance >= length) { |
| | 517 | @memcpy(dest, src); |
| | 518 | } else if (distance == 1) { |
| | 519 | // Repeating copy of single byte |
| | 520 | @memset(dest, src[0]); |
| | 521 | } else { |
| | 522 | // Repeating copy of multiple bytes |
| | 523 | for (dest, src) |*d, s| d.* = s; |
| | 524 | } |
| 515 | } | 525 | } |
| 516 | | 526 | |
| 517 | fn peekBits(d: *Decompress, n: u4) !u16 { | 527 | fn peekBits(d: *Decompress, n: u4) !u16 { |
| ... | @@ -603,31 +613,9 @@ fn readFixedCode(d: *Decompress) !u16 { | ... | @@ -603,31 +613,9 @@ fn readFixedCode(d: *Decompress) !u16 { |
| 603 | }; | 613 | }; |
| 604 | } | 614 | } |
| 605 | | 615 | |
| 606 | pub const Symbol = packed struct { | 616 | pub const Symbol = packed struct(u16) { |
| 607 | pub const Kind = enum(u2) { | 617 | value: u12 = 0, |
| 608 | literal, | | |
| 609 | end_of_block, | | |
| 610 | match, | | |
| 611 | }; | | |
| 612 | | | |
| 613 | symbol: u8 = 0, // symbol from alphabet | | |
| 614 | code_bits: u4 = 0, // number of bits in code 0-15 | 618 | code_bits: u4 = 0, // number of bits in code 0-15 |
| 615 | kind: Kind = .literal, | | |
| 616 | | | |
| 617 | code: u16 = 0, // huffman code of the symbol | | |
| 618 | next: u16 = 0, // pointer to the next symbol in linked list | | |
| 619 | // it is safe to use 0 as null pointer, when sorted 0 has shortest code and fits into lookup | | |
| 620 | | | |
| 621 | // Sorting less than function. | | |
| 622 | pub fn asc(_: void, a: Symbol, b: Symbol) bool { | | |
| 623 | if (a.code_bits == b.code_bits) { | | |
| 624 | if (a.kind == b.kind) { | | |
| 625 | return a.symbol < b.symbol; | | |
| 626 | } | | |
| 627 | return @intFromEnum(a.kind) < @intFromEnum(b.kind); | | |
| 628 | } | | |
| 629 | return a.code_bits < b.code_bits; | | |
| 630 | } | | |
| 631 | }; | 619 | }; |
| 632 | | 620 | |
| 633 | pub const LiteralDecoder = HuffmanDecoder(286, 15, 9); | 621 | pub const LiteralDecoder = HuffmanDecoder(286, 15, 9); |
| ... | @@ -646,69 +634,85 @@ pub const CodegenDecoder = HuffmanDecoder(19, 7, 7); | ... | @@ -646,69 +634,85 @@ pub const CodegenDecoder = HuffmanDecoder(19, 7, 7); |
| 646 | /// Small lookup table is optimization for faster search. | 634 | /// Small lookup table is optimization for faster search. |
| 647 | /// It is variation of the algorithm explained in [zlib](https://github.com/madler/zlib/blob/643e17b7498d12ab8d15565662880579692f769d/doc/algorithm.txt#L92) | 635 | /// It is variation of the algorithm explained in [zlib](https://github.com/madler/zlib/blob/643e17b7498d12ab8d15565662880579692f769d/doc/algorithm.txt#L92) |
| 648 | /// with difference that we here use statically allocated arrays. | 636 | /// with difference that we here use statically allocated arrays. |
| 649 | /// | | |
| 650 | fn HuffmanDecoder( | 637 | fn HuffmanDecoder( |
| 651 | comptime alphabet_size: u16, | 638 | comptime alphabet_size: u16, |
| 652 | comptime max_code_bits: u4, | 639 | comptime max_code_bits: u4, |
| 653 | comptime lookup_bits: u4, | 640 | comptime lookup_bits: u4, |
| 654 | ) type { | 641 | ) type { |
| 655 | const lookup_shift = max_code_bits - lookup_bits; | 642 | const lookup_shift = max_code_bits - lookup_bits; |
| | 643 | const lookup_mask = (1 << lookup_bits) - 1; |
| 656 | | 644 | |
| 657 | return struct { | 645 | return struct { |
| 658 | // all symbols in alaphabet, sorted by code_len, symbol | | |
| 659 | symbols: [alphabet_size]Symbol = undefined, | | |
| 660 | // lookup table code -> symbol | 646 | // lookup table code -> symbol |
| | 647 | // for values with code_bits == 0, symbol is the index of the first node in linked |
| | 648 | // if the index of the first node is 0xfff, it is an invalid code |
| 661 | lookup: [1 << lookup_bits]Symbol = undefined, | 649 | lookup: [1 << lookup_bits]Symbol = undefined, |
| | 650 | linked: if (lookup_bits == max_code_bits) void else [alphabet_size]struct { |
| | 651 | // sym.value is the next index in linked where the current index ends the chain |
| | 652 | // the actual symbol is this nodes's index |
| | 653 | sym: Symbol, |
| | 654 | code: u16, |
| | 655 | } = undefined, |
| 662 | | 656 | |
| 663 | const Self = @This(); | 657 | const Self = @This(); |
| 664 | | 658 | |
| | 659 | fn reverseIdx(idx: usize) u16 { |
| | 660 | return @bitReverse(@as(@Int(.unsigned, lookup_bits), @intCast(idx))); |
| | 661 | } |
| | 662 | |
| 665 | /// Generates symbols and lookup tables from list of code lens for each symbol. | 663 | /// Generates symbols and lookup tables from list of code lens for each symbol. |
| 666 | pub fn generate(self: *Self, lens: []const u4) !void { | 664 | pub fn generate(self: *Self, lens: []const u4) !void { |
| 667 | try checkCompleteness(lens); | 665 | try checkCompleteness(lens); |
| 668 | | 666 | |
| 669 | // init alphabet with code_bits | 667 | var buckets: [1 + @as(usize, max_code_bits)][alphabet_size]Symbol = undefined; |
| 670 | for (self.symbols, 0..) |_, i| { | 668 | var bucket_len: [buckets.len]u16 = @splat(0); |
| 671 | const cb: u4 = if (i < lens.len) lens[i] else 0; | 669 | for (0.., lens) |symbol, bits| { |
| 672 | self.symbols[i] = if (i < 256) | 670 | buckets[bits][bucket_len[bits]] = .{ |
| 673 | .{ .kind = .literal, .symbol = @intCast(i), .code_bits = cb } | 671 | .value = @intCast(symbol), |
| 674 | else if (i == 256) | 672 | .code_bits = bits, |
| 675 | .{ .kind = .end_of_block, .symbol = 0xff, .code_bits = cb } | 673 | }; |
| 676 | else | 674 | bucket_len[bits] += 1; |
| 677 | .{ .kind = .match, .symbol = @intCast(i - 257), .code_bits = cb }; | | |
| 678 | } | | |
| 679 | std.sort.heap(Symbol, &self.symbols, {}, Symbol.asc); | | |
| 680 | | | |
| 681 | // reset lookup table | | |
| 682 | for (0..self.lookup.len) |i| { | | |
| 683 | self.lookup[i] = .{}; | | |
| 684 | } | 675 | } |
| 685 | | 676 | |
| 686 | // assign code to symbols | | |
| 687 | // reference: https://youtu.be/9_YEGLe33NA?list=PLU4IQLU9e_OrY8oASHx0u3IXAL9TOdidm&t=2639 | | |
| 688 | var code: u16 = 0; | 677 | var code: u16 = 0; |
| 689 | var idx: u16 = 0; | 678 | var idx: u16 = 0; |
| 690 | for (&self.symbols, 0..) |*sym, pos| { | 679 | for (1..lookup_bits + 1) |bits| { |
| 691 | if (sym.code_bits == 0) continue; // skip unused | 680 | const inc = @as(u16, 1) << @intCast(max_code_bits - bits); |
| 692 | sym.code = code; | 681 | for (buckets[bits][0..bucket_len[bits]]) |lookup_sym| { |
| 693 | | 682 | const next_code = code + inc; |
| 694 | const next_code = code + (@as(u16, 1) << (max_code_bits - sym.code_bits)); | 683 | const next_idx = next_code >> lookup_shift; |
| 695 | const next_idx = next_code >> lookup_shift; | 684 | for (idx..next_idx) |i| { |
| 696 | | 685 | self.lookup[reverseIdx(i)] = lookup_sym; |
| 697 | if (next_idx > self.lookup.len or idx >= self.lookup.len) break; | 686 | } |
| 698 | if (sym.code_bits <= lookup_bits) { | 687 | code = next_code; |
| 699 | // fill small lookup table | 688 | idx = next_idx; |
| 700 | for (idx..next_idx) |j| | 689 | } |
| 701 | self.lookup[j] = sym.*; | 690 | } |
| 702 | } else { | 691 | for (lookup_bits + 1..buckets.len) |bits| { |
| 703 | // insert into linked table starting at root | 692 | const inc = @as(u16, 1) << @intCast(max_code_bits - bits); |
| 704 | const root = &self.lookup[idx]; | 693 | for (buckets[bits][0..bucket_len[bits]]) |linked_sym| { |
| 705 | const root_next = root.next; | 694 | const next_code = code + inc; |
| 706 | root.next = @intCast(pos); | 695 | const next_idx = next_code >> lookup_shift; |
| 707 | sym.next = root_next; | 696 | |
| | 697 | const ri = reverseIdx(idx); |
| | 698 | const next: Symbol = .{ |
| | 699 | .value = self.lookup[ri].value, |
| | 700 | .code_bits = linked_sym.code_bits, |
| | 701 | }; |
| | 702 | self.linked[linked_sym.value] = .{ |
| | 703 | .sym = next, |
| | 704 | .code = @bitReverse(@as(@Int(.unsigned, max_code_bits), @intCast(code))), |
| | 705 | }; |
| | 706 | self.lookup[ri] = .{ .value = linked_sym.value, .code_bits = 0 }; |
| | 707 | |
| | 708 | code = next_code; |
| | 709 | idx = next_idx; |
| 708 | } | 710 | } |
| | 711 | } |
| 709 | | 712 | |
| 710 | idx = next_idx; | 713 | // Invalid codes |
| 711 | code = next_code; | 714 | for (idx..self.lookup.len) |i| { |
| | 715 | self.lookup[reverseIdx(i)] = .{ .value = 0xfff, .code_bits = 0 }; |
| 712 | } | 716 | } |
| 713 | } | 717 | } |
| 714 | | 718 | |
| ... | @@ -748,23 +752,25 @@ fn HuffmanDecoder( | ... | @@ -748,23 +752,25 @@ fn HuffmanDecoder( |
| 748 | /// Finds symbol for lookup table code. | 752 | /// Finds symbol for lookup table code. |
| 749 | pub fn find(self: *Self, code: u16) !Symbol { | 753 | pub fn find(self: *Self, code: u16) !Symbol { |
| 750 | // try to find in lookup table | 754 | // try to find in lookup table |
| 751 | const idx = code >> lookup_shift; | 755 | const idx = code & lookup_mask; |
| 752 | const sym = self.lookup[idx]; | 756 | const sym = self.lookup[idx]; |
| 753 | if (sym.code_bits != 0) return sym; | 757 | if (sym.code_bits != 0) return sym; |
| 754 | // if not use linked list of symbols with same prefix | 758 | // if not use linked list of symbols with same prefix |
| 755 | return self.findLinked(code, sym.next); | 759 | return self.findLinked(code, sym.value); |
| 756 | } | 760 | } |
| 757 | | 761 | |
| 758 | fn findLinked(self: *Self, code: u16, start: u16) !Symbol { | 762 | fn findLinked(self: *Self, code: u16, start: u16) !Symbol { |
| | 763 | if (start == 0xfff) return error.InvalidCode; |
| | 764 | if (lookup_bits == max_code_bits) unreachable; |
| 759 | var pos = start; | 765 | var pos = start; |
| 760 | while (pos > 0) { | 766 | while (true) { |
| 761 | const sym = self.symbols[pos]; | 767 | const node = self.linked[pos]; |
| 762 | const shift = max_code_bits - sym.code_bits; | 768 | const shift = -%node.sym.code_bits; |
| 763 | // compare code_bits number of upper bits | 769 | // compare code_bits number of upper bits |
| 764 | if ((code ^ sym.code) >> shift == 0) return sym; | 770 | if ((code ^ node.code) << shift == 0) |
| 765 | pos = sym.next; | 771 | return .{ .value = @intCast(pos), .code_bits = node.sym.code_bits }; |
| | 772 | pos = node.sym.value; |
| 766 | } | 773 | } |
| 767 | return error.InvalidCode; | | |
| 768 | } | 774 | } |
| 769 | }; | 775 | }; |
| 770 | } | 776 | } |
| ... | @@ -775,74 +781,49 @@ test "init/find" { | ... | @@ -775,74 +781,49 @@ test "init/find" { |
| 775 | var h: CodegenDecoder = .{}; | 781 | var h: CodegenDecoder = .{}; |
| 776 | try h.generate(&code_lens); | 782 | try h.generate(&code_lens); |
| 777 | | 783 | |
| 778 | const expected = [_]struct { | | |
| 779 | sym: Symbol, | | |
| 780 | code: u16, | | |
| 781 | }{ | | |
| 782 | .{ | | |
| 783 | .code = 0b00_00000, | | |
| 784 | .sym = .{ .symbol = 3, .code_bits = 2 }, | | |
| 785 | }, | | |
| 786 | .{ | | |
| 787 | .code = 0b01_00000, | | |
| 788 | .sym = .{ .symbol = 18, .code_bits = 2 }, | | |
| 789 | }, | | |
| 790 | .{ | | |
| 791 | .code = 0b100_0000, | | |
| 792 | .sym = .{ .symbol = 1, .code_bits = 3 }, | | |
| 793 | }, | | |
| 794 | .{ | | |
| 795 | .code = 0b101_0000, | | |
| 796 | .sym = .{ .symbol = 4, .code_bits = 3 }, | | |
| 797 | }, | | |
| 798 | .{ | | |
| 799 | .code = 0b110_0000, | | |
| 800 | .sym = .{ .symbol = 17, .code_bits = 3 }, | | |
| 801 | }, | | |
| 802 | .{ | | |
| 803 | .code = 0b1110_000, | | |
| 804 | .sym = .{ .symbol = 0, .code_bits = 4 }, | | |
| 805 | }, | | |
| 806 | .{ | | |
| 807 | .code = 0b1111_000, | | |
| 808 | .sym = .{ .symbol = 16, .code_bits = 4 }, | | |
| 809 | }, | | |
| 810 | }; | | |
| 811 | | | |
| 812 | // unused symbols | | |
| 813 | for (0..12) |i| { | | |
| 814 | try testing.expectEqual(0, h.symbols[i].code_bits); | | |
| 815 | } | | |
| 816 | // used, from index 12 | | |
| 817 | for (expected, 12..) |e, i| { | | |
| 818 | try testing.expectEqual(e.sym.symbol, h.symbols[i].symbol); | | |
| 819 | try testing.expectEqual(e.sym.code_bits, h.symbols[i].code_bits); | | |
| 820 | const sym_from_code = try h.find(e.code); | | |
| 821 | try testing.expectEqual(e.sym.symbol, sym_from_code.symbol); | | |
| 822 | } | | |
| 823 | | | |
| 824 | // All possible codes for each symbol. | 784 | // All possible codes for each symbol. |
| 825 | // Lookup table has 126 elements, to cover all possible 7 bit codes. | 785 | // Lookup table has 126 elements, to cover all possible 7 bit codes. |
| 826 | for (0b0000_000..0b0100_000) |c| // 0..32 (32) | 786 | for (0b0000_000..0b0100_000) |c| // 0..32 (32) |
| 827 | try testing.expectEqual(3, (try h.find(@intCast(c))).symbol); | 787 | try testing.expectEqual( |
| | 788 | Symbol{ .value = 3, .code_bits = 2 }, |
| | 789 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 790 | ); |
| 828 | | 791 | |
| 829 | for (0b0100_000..0b1000_000) |c| // 32..64 (32) | 792 | for (0b0100_000..0b1000_000) |c| // 32..64 (32) |
| 830 | try testing.expectEqual(18, (try h.find(@intCast(c))).symbol); | 793 | try testing.expectEqual( |
| | 794 | Symbol{ .value = 18, .code_bits = 2 }, |
| | 795 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 796 | ); |
| 831 | | 797 | |
| 832 | for (0b1000_000..0b1010_000) |c| // 64..80 (16) | 798 | for (0b1000_000..0b1010_000) |c| // 64..80 (16) |
| 833 | try testing.expectEqual(1, (try h.find(@intCast(c))).symbol); | 799 | try testing.expectEqual( |
| | 800 | Symbol{ .value = 1, .code_bits = 3 }, |
| | 801 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 802 | ); |
| 834 | | 803 | |
| 835 | for (0b1010_000..0b1100_000) |c| // 80..96 (16) | 804 | for (0b1010_000..0b1100_000) |c| // 80..96 (16) |
| 836 | try testing.expectEqual(4, (try h.find(@intCast(c))).symbol); | 805 | try testing.expectEqual( |
| | 806 | Symbol{ .value = 4, .code_bits = 3 }, |
| | 807 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 808 | ); |
| 837 | | 809 | |
| 838 | for (0b1100_000..0b1110_000) |c| // 96..112 (16) | 810 | for (0b1100_000..0b1110_000) |c| // 96..112 (16) |
| 839 | try testing.expectEqual(17, (try h.find(@intCast(c))).symbol); | 811 | try testing.expectEqual( |
| | 812 | Symbol{ .value = 17, .code_bits = 3 }, |
| | 813 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 814 | ); |
| 840 | | 815 | |
| 841 | for (0b1110_000..0b1111_000) |c| // 112..120 (8) | 816 | for (0b1110_000..0b1111_000) |c| // 112..120 (8) |
| 842 | try testing.expectEqual(0, (try h.find(@intCast(c))).symbol); | 817 | try testing.expectEqual( |
| | 818 | Symbol{ .value = 0, .code_bits = 4 }, |
| | 819 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 820 | ); |
| 843 | | 821 | |
| 844 | for (0b1111_000..0b1_0000_000) |c| // 120...128 (8) | 822 | for (0b1111_000..0b1_0000_000) |c| // 120...128 (8) |
| 845 | try testing.expectEqual(16, (try h.find(@intCast(c))).symbol); | 823 | try testing.expectEqual( |
| | 824 | Symbol{ .value = 16, .code_bits = 4 }, |
| | 825 | try h.find(@bitReverse(@as(u7, @intCast(c)))), |
| | 826 | ); |
| 846 | } | 827 | } |
| 847 | | 828 | |
| 848 | test "encode/decode literals" { | 829 | test "encode/decode literals" { |
| ... | @@ -867,8 +848,8 @@ test "encode/decode literals" { | ... | @@ -867,8 +848,8 @@ test "encode/decode literals" { |
| 867 | if (bits == 0) continue; | 848 | if (bits == 0) continue; |
| 868 | for (0..1 << (max_bits - bits)) |extra| { | 849 | for (0..1 << (max_bits - bits)) |extra| { |
| 869 | const full = (@as(u16, code) << (max_bits - bits)) | @as(u16, @intCast(extra)); | 850 | const full = (@as(u16, code) << (max_bits - bits)) | @as(u16, @intCast(extra)); |
| 870 | const symbol = try decoder.find(full); | 851 | const symbol = try decoder.find(@bitReverse(@as(u5, @intCast(full)))); |
| 871 | try testing.expectEqual(i, symbol.symbol); | 852 | try testing.expectEqual(i, symbol.value); |
| 872 | try testing.expectEqual(bits, symbol.code_bits); | 853 | try testing.expectEqual(bits, symbol.code_bits); |
| 873 | } | 854 | } |
| 874 | } | 855 | } |