Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize decoding of fixed huffman blocks #38

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions src/decompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub struct Decompressor {
queued_rle: Option<(u8, usize)>,
queued_backref: Option<(usize, usize)>,
last_block: bool,
fixed_table: bool,

state: State,
checksum: Adler32,
Expand Down Expand Up @@ -145,6 +146,7 @@ impl Decompressor {
state: State::ZlibHeader,
last_block: false,
ignore_adler32: false,
fixed_table: false,
}
}

Expand Down Expand Up @@ -182,7 +184,7 @@ impl Decompressor {

fn read_block_header(&mut self, remaining_input: &mut &[u8]) -> Result<(), DecompressionError> {
self.fill_buffer(remaining_input);
if self.nbits < 3 {
if self.nbits < 10 {
return Ok(());
}

Expand All @@ -209,8 +211,35 @@ impl Decompressor {
}
0b01 => {
self.consume_bits(3);
// TODO: Do this statically rather than every time.
Self::build_tables(288, &FIXED_CODE_LENGTHS, &mut self.compression)?;

// Check for an entirely empty blocks which can happen if there are "partial
// flushes" in the deflate stream. With fixed huffman codes, the EOF symbol is
// 7-bits of zeros so we peak ahead and see if the next 7-bits are all zero.
if self.peak_bits(7) == 0 {
self.consume_bits(7);
if self.last_block {
self.state = State::Checksum;
return Ok(());
}

// At this point we've consumed the entire block and need to read the next block
// header. If tail call optimization were guaranteed, we could just recurse
// here. But without it, a long sequence of empty fixed-blocks might cause a
// stack overflow. Instead, we consume all empty blocks in a loop and then
// recurse. This is the only recursive call this function, and thus is safe.
while self.nbits >= 10 && self.peak_bits(10) == 0b010 {
self.consume_bits(10);
self.fill_buffer(remaining_input);
}
return self.read_block_header(remaining_input);
}

// Build decoding tables if the previous block wasn't also a fixed block.
if !self.fixed_table {
self.fixed_table = true;
Self::build_tables(288, &FIXED_CODE_LENGTHS, &mut self.compression)?;
}

self.state = State::CompressedData;
Ok(())
}
Expand All @@ -231,6 +260,7 @@ impl Decompressor {

self.consume_bits(17);
self.state = State::CodeLengthCodes;
self.fixed_table = false;
Ok(())
}
0b11 => Err(DecompressionError::InvalidBlockType),
Expand Down