Skip to content

Commit 9f1fc5e

Browse files
committed
fix(inflate): use function instead of lookup table for distance extra bits for tiny space/perf saving and fix clippy warnings
1 parent 0a33eff commit 9f1fc5e

File tree

4 files changed

+100
-32
lines changed

4 files changed

+100
-32
lines changed

miniz_oxide/src/inflate/core.rs

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const MAX_HUFF_SYMBOLS_0: usize = 288;
9999
/// The length of the second (distance) huffman table.
100100
const MAX_HUFF_SYMBOLS_1: usize = 32;
101101
/// The length of the last (huffman code length) huffman table.
102-
const _MAX_HUFF_SYMBOLS_2: usize = 19;
102+
// const _MAX_HUFF_SYMBOLS_2: usize = 19;
103103
/// The maximum length of a code that can be looked up in the fast lookup table.
104104
const FAST_LOOKUP_BITS: u8 = 10;
105105
/// The size of the fast lookup table.
@@ -337,7 +337,6 @@ impl State {
337337

338338
use self::State::*;
339339

340-
// Not sure why miniz uses 32-bit values for these, maybe alignment/cache again?
341340
// # Optimization
342341
// We add a extra value at the end and make the tables 32 elements long
343342
// so we can use a mask to avoid bounds checks.
@@ -362,18 +361,20 @@ const LENGTH_EXTRA: [u8; 32] = [
362361

363362
/// Base length for each distance code.
364363
#[rustfmt::skip]
365-
const DIST_BASE: [u16; 32] = [
364+
const DIST_BASE: [u16; 30] = [
366365
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33,
367366
49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537,
368-
2049, 3073, 4097, 6145, 8193, 12_289, 16_385, 24_577, 32_768, 32_768
367+
2049, 3073, 4097, 6145, 8193, 12_289, 16_385, 24_577
369368
];
370369

371-
/// Number of extra bits for each distance code.
372-
#[rustfmt::skip]
373-
const DIST_EXTRA: [u8; 32] = [
374-
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
375-
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 13, 13
376-
];
370+
/// Get the number of extra bits used for a distance code.
371+
/// (Code numbers above `NUM_DISTANCE_CODES` will give some garbage
372+
/// value.)
373+
const fn num_extra_bits_for_distance_code(code: u8) -> u8 {
374+
// This can be easily calculated without a lookup.
375+
let c = code >> 1;
376+
c - (c != 0) as u8
377+
}
377378

378379
/// The mask used when indexing the base/extra arrays.
379380
const BASE_EXTRA_MASK: usize = 32 - 1;
@@ -1092,7 +1093,7 @@ fn decompress_fast(
10921093
break 'o TINFLStatus::Failed;
10931094
}
10941095

1095-
l.num_extra = u32::from(DIST_EXTRA[symbol as usize]);
1096+
l.num_extra = u32::from(num_extra_bits_for_distance_code(symbol as u8));
10961097
l.dist = u32::from(DIST_BASE[symbol as usize]);
10971098
} else {
10981099
state.begin(InvalidCodeLen);
@@ -1149,18 +1150,18 @@ fn decompress_fast(
11491150
///
11501151
/// * The offset given by `out_pos` indicates where in the output buffer slice writing should start.
11511152
/// * If [`TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF`] is not set, the output buffer is used in a
1152-
/// wrapping manner, and it's size is required to be a power of 2.
1153+
/// wrapping manner, and it's size is required to be a power of 2.
11531154
/// * The decompression function normally needs access to 32KiB of the previously decompressed data
1154-
///(or to the beginning of the decompressed data if less than 32KiB has been decompressed.)
1155+
/// (or to the beginning of the decompressed data if less than 32KiB has been decompressed.)
11551156
/// - If this data is not available, decompression may fail.
11561157
/// - Some deflate compressors allow specifying a window size which limits match distances to
1157-
/// less than this, or alternatively an RLE mode where matches will only refer to the previous byte
1158-
/// and thus allows a smaller output buffer. The window size can be specified in the zlib
1159-
/// header structure, however, the header data should not be relied on to be correct.
1158+
/// less than this, or alternatively an RLE mode where matches will only refer to the previous byte
1159+
/// and thus allows a smaller output buffer. The window size can be specified in the zlib
1160+
/// header structure, however, the header data should not be relied on to be correct.
11601161
///
11611162
/// `flags` indicates settings and status to the decompression function.
11621163
/// * The [`TINFL_FLAG_HAS_MORE_INPUT`] has to be specified if more compressed data is to be provided
1163-
/// in a subsequent call to this function.
1164+
/// in a subsequent call to this function.
11641165
/// * See the the [`inflate_flags`] module for details on other flags.
11651166
///
11661167
/// # Returns
@@ -1177,7 +1178,7 @@ pub fn decompress(
11771178
flags: u32,
11781179
) -> (TINFLStatus, usize, usize) {
11791180
let out_buf_size_mask = if flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF != 0 {
1180-
usize::max_value()
1181+
usize::MAX
11811182
} else {
11821183
// In the case of zero len, any attempt to write would produce HasMoreOutput,
11831184
// so to gracefully process the case of there really being no output,
@@ -1610,16 +1611,19 @@ pub fn decompress(
16101611
// Try to read a huffman code from the input buffer and look up what
16111612
// length code the decoded symbol refers to.
16121613
decode_huffman_code(r, &mut l, DIST_TABLE, flags, &mut in_iter, |_r, l, symbol| {
1614+
// # Optimizaton - transform the value into usize here before the check so
1615+
// the compiler can optimize the bounds check later - ideally it should
1616+
// know that the value can't be negative from earlier in the
1617+
// decode_huffman_code function but it seems it may not be able
1618+
// to make the assumption that it can't be negative and thus
1619+
// overflow if it's converted after the check.
1620+
let symbol = symbol as usize;
16131621
if symbol > 29 {
16141622
// Invalid distance code.
16151623
return Action::Jump(InvalidDist)
16161624
}
1617-
// # Optimization
1618-
// Mask the value to avoid bounds checks
1619-
// We could use get_unchecked later if can statically verify that
1620-
// this will never go out of bounds.
1621-
l.num_extra = u32::from(DIST_EXTRA[symbol as usize & BASE_EXTRA_MASK]);
1622-
l.dist = u32::from(DIST_BASE[symbol as usize & BASE_EXTRA_MASK]);
1625+
l.num_extra = u32::from(num_extra_bits_for_distance_code(symbol as u8));
1626+
l.dist = u32::from(DIST_BASE[symbol]);
16231627
if l.num_extra != 0 {
16241628
// ReadEXTRA_BITS_DISTACNE
16251629
Action::Jump(ReadExtraBitsDistance)
@@ -2051,4 +2055,18 @@ mod test {
20512055
let res = decompress(&mut r, &encoded, &mut output_buf, 0, flags);
20522056
assert_eq!(res, (TINFLStatus::HasMoreOutput, 2, 0));
20532057
}
2058+
2059+
#[test]
2060+
fn dist_extra_bits() {
2061+
use self::num_extra_bits_for_distance_code;
2062+
// Number of extra bits for each distance code.
2063+
const DIST_EXTRA: [u8; 29] = [
2064+
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12,
2065+
12, 13,
2066+
];
2067+
2068+
for (i, &dist) in DIST_EXTRA.iter().enumerate() {
2069+
assert_eq!(dist, num_extra_bits_for_distance_code(i as u8));
2070+
}
2071+
}
20542072
}

miniz_oxide/src/inflate/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
33
#[cfg(feature = "with-alloc")]
44
use crate::alloc::{boxed::Box, vec, vec::Vec};
5-
use ::core::usize;
65
#[cfg(all(feature = "std", feature = "with-alloc"))]
76
use std::error::Error;
87

@@ -123,7 +122,7 @@ fn decompress_error(status: TINFLStatus, output: Vec<u8>) -> Result<Vec<u8>, Dec
123122
#[inline]
124123
#[cfg(feature = "with-alloc")]
125124
pub fn decompress_to_vec(input: &[u8]) -> Result<Vec<u8>, DecompressError> {
126-
decompress_to_vec_inner(input, 0, usize::max_value())
125+
decompress_to_vec_inner(input, 0, usize::MAX)
127126
}
128127

129128
/// Decompress the deflate-encoded data (with a zlib wrapper) in `input` to a vector.
@@ -139,7 +138,7 @@ pub fn decompress_to_vec_zlib(input: &[u8]) -> Result<Vec<u8>, DecompressError>
139138
decompress_to_vec_inner(
140139
input,
141140
inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER,
142-
usize::max_value(),
141+
usize::MAX,
143142
)
144143
}
145144

miniz_oxide/src/inflate/stream.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ pub fn inflate(
227227
if (flush == MZFlush::Finish) && first_call {
228228
decomp_flags |= inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
229229

230+
// The caller is indicating that they want to finish the compression and this is the first call with the current stream
231+
// so we can simply write directly to the output buffer.
232+
// If there is not enough space for all of the decompressed data we will end up with a failure regardless.
230233
let status = decompress(&mut state.decomp, next_in, next_out, 0, decomp_flags);
231234
let in_bytes = status.1;
232235
let out_bytes = status.2;
@@ -435,7 +438,12 @@ mod test {
435438
let mut part_in = 0;
436439
let mut part_out = 0;
437440
for i in 1..=encoded.len() {
438-
let res = inflate(&mut state, &encoded[part_in..i], &mut out[part_out..], MZFlush::None);
441+
let res = inflate(
442+
&mut state,
443+
&encoded[part_in..i],
444+
&mut out[part_out..],
445+
MZFlush::None,
446+
);
439447
let status = res.status.expect("Failed to decompress!");
440448
if i == encoded.len() {
441449
assert_eq!(status, MZStatus::StreamEnd);
@@ -451,7 +459,6 @@ mod test {
451459
assert_eq!(state.decompressor().adler32(), Some(459605011));
452460
}
453461

454-
455462
// Inflate part of a stream and clone the inflate state.
456463
// Discard the original state and resume the stream from the clone.
457464
#[test]
@@ -474,14 +481,21 @@ mod test {
474481
drop(state);
475482

476483
// Resume the stream using the cloned state
477-
let res2 = inflate(&mut resume, &encoded[res1.bytes_consumed..], &mut out[res1.bytes_written..], MZFlush::Finish);
484+
let res2 = inflate(
485+
&mut resume,
486+
&encoded[res1.bytes_consumed..],
487+
&mut out[res1.bytes_written..],
488+
MZFlush::Finish,
489+
);
478490
let status = res2.status.expect("Failed to decompress!");
479491
assert_eq!(status, MZStatus::StreamEnd);
480492

481493
assert_eq!(res1.bytes_consumed + res2.bytes_consumed, encoded.len());
482494
assert_eq!(res1.bytes_written + res2.bytes_written, decoded.len());
483-
assert_eq!(&out[..res1.bytes_written + res2.bytes_written as usize], decoded);
495+
assert_eq!(
496+
&out[..res1.bytes_written + res2.bytes_written as usize],
497+
decoded
498+
);
484499
assert_eq!(resume.decompressor().adler32(), Some(459605011));
485500
}
486-
487501
}

miniz_oxide/tests/test.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,43 @@ fn issue_143_return_buf_error_on_finish_without_end_header() {
250250
assert_eq!(inflate_result.status.unwrap_err(), MZError::Buf)
251251
}
252252

253+
/*
254+
#[test]
255+
fn partial_decompression_imap_issue_158() {
256+
use miniz_oxide::inflate::stream::{inflate, InflateState};
257+
use miniz_oxide::{DataFormat, MZFlush};
258+
use std::string;
259+
260+
// Decompresses to
261+
// "* QUOTAROOT INBOX \"User quota\"\r\n* QUOTA \"User quota\" (STORAGE 76 307200)\r\nA0001 OK Getquotaroot completed (0.001 + 0.000 secs).\r\n"
262+
let input = vec![
263+
210, 82, 8, 12, 245, 15, 113, 12, 242, 247, 15, 81, 240, 244, 115, 242, 143, 80, 80, 10,
264+
45, 78, 45, 82, 40, 44, 205, 47, 73, 84, 226, 229, 210, 130, 200, 163, 136, 42, 104, 4,
265+
135, 248, 7, 57, 186, 187, 42, 152, 155, 41, 24, 27, 152, 27, 25, 24, 104, 242, 114, 57,
266+
26, 24, 24, 24, 42, 248, 123, 43, 184, 167, 150, 128, 213, 21, 229, 231, 151, 40, 36, 231,
267+
231, 22, 228, 164, 150, 164, 166, 40, 104, 24, 232, 129, 20, 104, 43, 128, 104, 3, 133,
268+
226, 212, 228, 98, 77, 61, 94, 46, 0, 0, 0, 0, 255, 255,
269+
];
270+
271+
let mut inflate_stream = InflateState::new(DataFormat::Raw);
272+
let mut output = vec![0; 8];
273+
let result = inflate(&mut inflate_stream, &input, &mut output, MZFlush::None);
274+
275+
let out_string: String = string::String::from_utf8(output).unwrap();
276+
277+
println!("{}", out_string);
278+
println!("written {}", result.bytes_written);
279+
280+
assert!(result.status.is_ok());
281+
// Should not consume everything, there is not enough space in the buffer for the output.
282+
assert!(
283+
result.bytes_consumed < input.len(),
284+
"bytes consumed {:?}, input.len() {}",
285+
result.bytes_consumed,
286+
input.len()
287+
)
288+
}*/
289+
253290
/*
254291
#[test]
255292
fn large_file() {

0 commit comments

Comments
 (0)