Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Huffman

Only performs huffman compression on data, does no matching.

Compress.Huffman
pub const Huffman = struct

File

lib/std/compress/flate/Compress.zig:2096

Code

pub const Huffman = struct {
    /// After `finish` is called, all vtable calls with result in `error.WriteFailed`.
    writer: Writer,
    bit_writer: BitWriter,
    hasher: flate.Container.Hasher,

    const max_tokens: u16 = 65535 - 1; // one is reserved for EOF

    /// While there is no minimum buffer size, it is recommended
    /// to be at least `flate.max_window_len` to improve compression.
    ///
    /// It is asserted `output` has a capacity of at least 8 bytes.
    pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Huffman {
        assert(output.buffer.len > 8);

        try output.writeAll(container.header());
        return .{
            .writer = .{
                .buffer = buffer,
                .vtable = &.{
                    .drain = Huffman.drain,
                    .flush = Huffman.flush,
                    .rebase = Huffman.rebase,
                },
            },
            .bit_writer = .init(output),
            .hasher = .init(container),
        };
    }

    fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
        const h: *Huffman = @fieldParentPtr("writer", w);
        const min_block = @min(w.buffer.len, max_tokens);
        const pattern = data[data.len - 1];

        const data_bytes = Writer.countSplat(data, splat);
        const total_bytes = w.end + data_bytes;
        var rem_bytes = total_bytes;
        var rem_splat = splat;
        var rem_data = data;
        var rem_data_elem: []const u8 = w.buffered();

        assert(rem_bytes > min_block);
        while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final
            // also, it handles the case of `min_block` being zero (no buffer)
            const block_size: u16 = @min(rem_bytes, max_tokens);
            rem_bytes -= block_size;

            // Count frequencies
            comptime assert(max_tokens != 65535);
            var freqs: [257]u16 = @splat(0);
            freqs[256] = 1;

            const start_splat = rem_splat;
            const start_data = rem_data;
            const start_data_elem = rem_data_elem;

            var block_limit: Io.Limit = .limited(block_size);
            while (true) {
                const bytes = block_limit.sliceConst(rem_data_elem);
                const is_pattern = rem_splat != splat and bytes.len == pattern.len;

                const mul = if (!is_pattern) 1 else @backingInt(block_limit) / pattern.len;
                assert(mul != 0);
                if (is_pattern) assert(mul <= rem_splat + 1); // one more for `rem_data`

                for (bytes) |b| freqs[b] += @intCast(mul);
                rem_data_elem = rem_data_elem[bytes.len..];
                block_limit = block_limit.subtract(bytes.len * mul).?;

                if (rem_data_elem.len == 0) {
                    rem_data_elem = rem_data[0];
                    if (rem_data.len != 1) {
                        rem_data = rem_data[1..];
                    } else if (rem_splat >= mul) {
                        // if the counter was not the pattern, `mul` is always one, otherwise,
                        // `mul` contains `rem_data`,  however one more needs subtracted anyways
                        // since the next pattern is also being taken.
                        rem_splat -= mul;
                    } else {
                        // All of `data` has been consumed.
                        assert(block_limit == .nothing);
                        assert(rem_bytes == 0);
                        // Since `rem_bytes` and `block_limit` are zero, these won't be used.
                        rem_data = undefined;
                        rem_data_elem = undefined;
                        rem_splat = undefined;
                    }
                }
                if (block_limit == .nothing) break;
            }

            // Output block
            rem_splat = start_splat;
            rem_data = start_data;
            rem_data_elem = start_data_elem;
            block_limit = .limited(block_size);

            var codes_buf: CodesBuf = .init;
            if (try h.outputHeader(&freqs, &codes_buf, block_size, false)) |table| {
                while (true) {
                    const bytes = block_limit.sliceConst(rem_data_elem);
                    rem_data_elem = rem_data_elem[bytes.len..];
                    block_limit = block_limit.subtract(bytes.len).?;

                    h.hasher.update(bytes);
                    for (bytes) |b| {
                        try h.bit_writer.write(table.codes[b], table.bits[b]);
                    }

                    if (rem_data_elem.len == 0) {
                        rem_data_elem = rem_data[0];
                        if (rem_data.len != 1) {
                            rem_data = rem_data[1..];
                        } else if (rem_splat != 0) {
                            rem_splat -= 1;
                        } else {
                            // All of `data` has been consumed.
                            assert(block_limit == .nothing);
                            assert(rem_bytes == 0);
                            // Since `rem_bytes` and `block_limit` are zero, these won't be used.
                            rem_data = undefined;
                            rem_data_elem = undefined;
                            rem_splat = undefined;
                        }
                    }
                    if (block_limit == .nothing) break;
                }
                try h.bit_writer.write(table.codes[256], table.bits[256]);
            } else while (true) {
                // Store block

                // Write data that is not a full vector element
                const in_pattern = rem_splat != splat;
                const vec_elem_i, const in_data =
                    @subWithOverflow(data.len - (rem_data.len - @intFromBool(in_pattern)), 1);
                const is_elem = in_data == 0 and data[vec_elem_i].len == rem_data_elem.len;

                if (!is_elem or rem_data_elem.len > @backingInt(block_limit)) {
                    block_limit = block_limit.subtract(rem_data_elem.len) orelse {
                        try h.bit_writer.output.writeAll(rem_data_elem[0..@backingInt(block_limit)]);
                        h.hasher.update(rem_data_elem[0..@backingInt(block_limit)]);
                        rem_data_elem = rem_data_elem[@backingInt(block_limit)..];
                        assert(rem_data_elem.len != 0);
                        break;
                    };
                    try h.bit_writer.output.writeAll(rem_data_elem);
                    h.hasher.update(rem_data_elem);
                } else {
                    // Put `rem_data_elem` back in `rem_data`
                    if (!in_pattern) {
                        rem_data = data[vec_elem_i..];
                    } else {
                        rem_splat += 1;
                    }
                }
                rem_data_elem = undefined; // it is always updated below

                // Send through as much of the original vector as possible
                var vec_n: usize = 0;
                var vlimit = block_limit;
                const vec_splat = while (rem_data[vec_n..].len != 1) {
                    vlimit = vlimit.subtract(rem_data[vec_n].len) orelse break 1;
                    vec_n += 1;
                } else vec_splat: {
                    // For `pattern.len == 0`, the value of `vec_splat` does not matter.
                    const vec_splat = @backingInt(vlimit) / @max(1, pattern.len);
                    if (pattern.len != 0) assert(vec_splat <= rem_splat + 1);
                    vlimit = vlimit.subtract(pattern.len * vec_splat).?;
                    vec_n += 1;
                    break :vec_splat vec_splat;
                };

                const n = if (vec_n != 0) n: {
                    assert(@backingInt(block_limit) - @backingInt(vlimit) ==
                        Writer.countSplat(rem_data[0..vec_n], vec_splat));
                    break :n try h.bit_writer.output.writeSplat(rem_data[0..vec_n], vec_splat);
                } else 0; // Still go into the case below to advance the vector
                block_limit = block_limit.subtract(n).?;
                var consumed: Io.Limit = .limited(n);

                while (rem_data.len != 1) {
                    const elem = rem_data[0];
                    rem_data = rem_data[1..];
                    consumed = consumed.subtract(elem.len) orelse {
                        h.hasher.update(elem[0..@backingInt(consumed)]);
                        rem_data_elem = elem[@backingInt(consumed)..];
                        break;
                    };
                    h.hasher.update(elem);
                } else {
                    if (pattern.len == 0) {
                        // All of `data` has been consumed. However, the general
                        // case below does not work since it divides by zero.
                        assert(consumed == .nothing);
                        assert(block_limit == .nothing);
                        assert(rem_bytes == 0);
                        // Since `rem_bytes` and `block_limit` are zero, these won't be used.
                        rem_splat = undefined;
                        rem_data = undefined;
                        rem_data_elem = undefined;
                        break;
                    }

                    const splatted = @backingInt(consumed) / pattern.len;
                    const partial = @backingInt(consumed) % pattern.len;
                    for (0..splatted) |_| h.hasher.update(pattern);
                    h.hasher.update(pattern[0..partial]);

                    const taken_splat = splatted + 1;
                    if (rem_splat >= taken_splat) {
                        rem_splat -= taken_splat;
                        rem_data_elem = pattern[partial..];
                    } else {
                        // All of `data` has been consumed.
                        assert(partial == 0);
                        assert(block_limit == .nothing);
                        assert(rem_bytes == 0);
                        // Since `rem_bytes` and `block_limit` are zero, these won't be used.
                        rem_data = undefined;
                        rem_data_elem = undefined;
                        rem_splat = undefined;
                    }
                }

                if (block_limit == .nothing) break;
            }
        }

        if (rem_bytes > data_bytes) {
            assert(rem_bytes - data_bytes == rem_data_elem.len);
            assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]);
        }
        return w.consume(total_bytes - rem_bytes);
    }

    fn flush(w: *Writer) Writer.Error!void {
        errdefer w.* = .failing;
        const h: *Huffman = @fieldParentPtr("writer", w);
        try Huffman.rebaseInner(w, 0, w.buffer.len, false);
        try h.bit_writer.byteAlignBlocks();
    }

    pub fn finish(h: *Huffman) Writer.Error!void {
        defer h.writer = .failing;
        try Huffman.rebaseInner(&h.writer, 0, h.writer.buffer.len, true);
        try h.bit_writer.output.rebase(0, 1);
        h.bit_writer.byteAlign();
        try h.hasher.writeFooter(h.bit_writer.output);
    }

    fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
        errdefer w.* = .failing;
        try Huffman.rebaseInner(w, preserve, capacity, false);
    }

    fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
        const h: *Huffman = @fieldParentPtr("writer", w);
        assert(preserve + capacity <= w.buffer.len);
        if (eos) assert(capacity == w.buffer.len);

        const preserved = @min(w.end, preserve);
        var remaining = w.buffer[0 .. w.end - preserved];
        while (remaining.len > max_tokens) { // not >= so there is always a block down below
            const bytes = remaining[0..max_tokens];
            remaining = remaining[max_tokens..];
            try h.outputBytes(bytes, false);
        }

        // eos check required for empty block
        if (w.buffer.len - (remaining.len + preserved) < capacity or eos) {
            const bytes = remaining;
            remaining = &.{};
            try h.outputBytes(bytes, eos);
        }

        _ = w.consume(w.end - preserved - remaining.len);
    }

    fn outputBytes(h: *Huffman, bytes: []const u8, eos: bool) Writer.Error!void {
        comptime assert(max_tokens != 65535);
        assert(bytes.len <= max_tokens);
        var freqs: [257]u16 = @splat(0);
        freqs[256] = 1;
        for (bytes) |b| freqs[b] += 1;
        h.hasher.update(bytes);

        var codes_buf: CodesBuf = .init;
        if (try h.outputHeader(&freqs, &codes_buf, @intCast(bytes.len), eos)) |table| {
            for (bytes) |b| {
                try h.bit_writer.write(table.codes[b], table.bits[b]);
            }
            try h.bit_writer.write(table.codes[256], table.bits[256]);
        } else {
            try h.bit_writer.output.writeAll(bytes);
        }
    }

    const CodesBuf = struct {
        dyn_codes: [258]u16,
        dyn_bits: [258]u4,

        pub const init: CodesBuf = .{
            .dyn_codes = @as([257]u16, undefined) ++ .{0},
            .dyn_bits = @as([257]u4, @splat(0)) ++ .{1},
        };
    };

    /// Returns null if the block is stored.
    fn outputHeader(
        h: *Huffman,
        freqs: *const [257]u16,
        buf: *CodesBuf,
        bytes: u16,
        eos: bool,
    ) Writer.Error!?struct {
        codes: *const [257]u16,
        bits: *const [257]u4,
    } {
        assert(freqs[256] == 1);
        const dyn_codes_bitsize, _ = huffman.build(
            freqs,
            buf.dyn_codes[0..257],
            buf.dyn_bits[0..257],
            15,
            true,
        );

        var clen_values: [258]u8 = undefined;
        var clen_extra: [258]u8 = undefined;
        var clen_freqs: [19]u16 = @splat(0);
        const clen_len, const clen_extra_bitsize = buildClen(
            &buf.dyn_bits,
            &clen_values,
            &clen_extra,
            &clen_freqs,
        );

        var clen_codes: [19]u16 = undefined;
        var clen_bits: [19]u4 = @splat(0);
        const clen_codes_bitsize, _ = huffman.build(
            &clen_freqs,
            &clen_codes,
            &clen_bits,
            7,
            false,
        );
        const hclen = clenHlen(clen_freqs);

        const dynamic_bitsize = @as(u32, 14) +
            (4 + @as(u6, hclen)) * 3 + clen_codes_bitsize + clen_extra_bitsize +
            dyn_codes_bitsize;
        const fixed_bitsize = n: {
            const freq7 = 1; // eos
            var freq9: u16 = 0;
            for (freqs[144..256]) |f| freq9 += f;
            const freq8: u16 = bytes - freq9;
            break :n @as(u32, freq7) * 7 + @as(u32, freq8) * 8 + @as(u32, freq9) * 9;
        };
        const stored_bitsize = n: {
            const stored_align_bits = -%(h.bit_writer.buffered_n +% 3);
            break :n stored_align_bits + @as(u32, 32) + @as(u32, bytes) * 8;
        };

        if (stored_bitsize <= @min(dynamic_bitsize, fixed_bitsize)) {
            try h.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3);
            try h.bit_writer.output.rebase(0, 5);
            h.bit_writer.byteAlign();
            h.bit_writer.output.writeInt(u16, bytes, .little) catch unreachable;
            h.bit_writer.output.writeInt(u16, ~bytes, .little) catch unreachable;
            return null;
        }

        if (fixed_bitsize <= dynamic_bitsize) {
            try h.bit_writer.write(BlockHeader.int(.{ .final = eos, .kind = .fixed }), 3);
            return .{
                .codes = token.fixed_lit_codes[0..257],
                .bits = token.fixed_lit_bits[0..257],
            };
        } else {
            try h.bit_writer.write(BlockHeader.Dynamic.int(.{
                .regular = .{ .final = eos, .kind = .dynamic },
                .hlit = 0,
                .hdist = 0,
                .hclen = hclen,
            }), 17);
            try h.bit_writer.writeClen(
                hclen,
                clen_values[0..clen_len],
                clen_extra[0..clen_len],
                clen_codes,
                clen_bits,
            );
            return .{ .codes = buf.dyn_codes[0..257], .bits = buf.dyn_bits[0..257] };
        }
    }
}