• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vigna / dsi-bitstream-rs / 29653800427

17 Jul 2026 07:33PM UTC coverage: 58.294% (+0.5%) from 57.839%
29653800427

push

github

vigna
Limit peekable bits to half the buffer instead of peeking twice

Revert the two-refill peek_bits and the associated full-buffer handling in
read_bits/skip_bits/read_unary introduced on the peek_twice branch, restoring
the original single-refill hot paths verbatim. Instead, set PEEK_BITS to
exactly one word (half the two-word bit buffer) so that a peek needs at most
one refill and the bit buffer is never completely full. This keeps all the
other fixes from the branch (copy_to/copy_from, flush retry, into_inner,
counters, dispatch, std::io::Read, word adapters) while avoiding the
per-call branch that the full-buffer handling added to read_bits/peek_bits/
skip_bits on the default (u32) read word.

One word of peekable bits is sufficient for every decoding table; the
generated check_read_table asserts guarantee this at compile time. The read
table-sweep script is clamped so it never generates a table wider than the
read word's PEEK_BITS (e.g. no 9-bit gamma tables on an 8-bit read word).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

12 of 12 new or added lines in 1 file covered. (100.0%)

179 existing lines in 7 files now uncovered.

2228 of 3822 relevant lines covered (58.29%)

2626768.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.77
/src/impls/bit_reader.rs
1
/*
2
 * SPDX-FileCopyrightText: 2023 Tommaso Fontana
3
 * SPDX-FileCopyrightText: 2023 Inria
4
 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
5
 *
6
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
7
 */
8

9
use core::convert::Infallible;
10
#[cfg(feature = "mem_dbg")]
11
use mem_dbg::{MemDbg, MemSize};
12

13
use crate::codes::params::{DefaultReadParams, ReadParams};
14
use crate::traits::*;
15

16
/// An implementation of [`BitRead`] for a [`WordRead`] with word `u64` and of
17
/// [`BitSeek`] for a [`WordSeek`].
18
///
19
/// This implementation randomly accesses the underlying [`WordRead`] without
20
/// any buffering. It is usually slower than
21
/// [`BufBitReader`](crate::impls::BufBitReader).
22
///
23
/// The peek word is `u32`. The value returned by
24
/// [`peek_bits`](crate::traits::BitRead::peek_bits) contains at least 32 bits
25
/// (extended with zeros beyond end of stream), that is, a full peek word.
26
///
27
/// The additional type parameter `RP` is used to select the parameters for the
28
/// instantaneous codes, but the casual user should be happy with the default
29
/// value. See [`ReadParams`] for more details.
30
///
31
/// For additional flexibility, when the `std` feature is enabled, this
32
/// structure implements [`std::io::Read`]. Note that because of coherence
33
/// rules it is not possible to implement [`std::io::Read`] for a generic
34
/// [`BitRead`].
35

36
#[derive(Debug, Clone)]
37
#[cfg_attr(feature = "mem_dbg", derive(MemDbg, MemSize))]
38
pub struct BitReader<E: Endianness, WR, RP: ReadParams = DefaultReadParams> {
39
    /// The backend from which we will read words.
40
    backend: WR,
41
    /// The index of the current bit.
42
    bit_index: u64,
43
    _marker: core::marker::PhantomData<(E, RP)>,
44
}
45

46
impl<E: Endianness, WR, RP: ReadParams> BitReader<E, WR, RP> {
47
    /// Creates a new [`BitReader`] with the given word reader.
48
    #[must_use]
49
    pub const fn new(backend: WR) -> Self {
1,968✔
50
        Self {
51
            backend,
52
            bit_index: 0,
53
            _marker: core::marker::PhantomData,
54
        }
55
    }
56
}
57

58
impl<WR: WordRead<Word = u64> + WordSeek<Error = <WR as WordRead>::Error>, RP: ReadParams>
59
    BitRead<BE> for BitReader<BE, WR, RP>
60
{
61
    type Error = <WR as WordRead>::Error;
62
    type PeekWord = u32;
63
    const PEEK_BITS: usize = 32;
64

65
    #[inline]
66
    fn skip_bits(&mut self, n_bits: usize) -> Result<(), Self::Error> {
×
67
        self.bit_index += n_bits as u64;
×
68
        Ok(())
×
69
    }
70

71
    #[inline]
72
    fn read_bits(&mut self, num_bits: usize) -> Result<u64, Self::Error> {
26,972✔
73
        debug_assert!(num_bits <= 64);
53,944✔
74
        #[cfg(feature = "checks")]
75
        assert!(num_bits <= 64);
53,944✔
76

77
        if num_bits == 0 {
26,972✔
78
            return Ok(0);
246✔
79
        }
80

81
        self.backend.set_word_pos(self.bit_index / 64)?;
80,178✔
82
        let in_word_offset = (self.bit_index % 64) as usize;
53,452✔
83

84
        let res = if (in_word_offset + num_bits) <= 64 {
53,452✔
85
            // single word access
86
            let word = self.backend.read_word()?.to_be();
83,676✔
87
            (word << in_word_offset) >> (64 - num_bits)
20,919✔
88
        } else {
89
            // double word access
90
            let high_word = self.backend.read_word()?.to_be();
23,228✔
91
            let low_word = self.backend.read_word()?.to_be();
23,228✔
92
            let shamt1 = 64 - num_bits;
11,614✔
93
            let shamt2 = 128 - in_word_offset - num_bits;
11,614✔
94
            ((high_word << in_word_offset) >> shamt1) | (low_word >> shamt2)
11,614✔
95
        };
96
        self.bit_index += num_bits as u64;
26,726✔
97
        Ok(res)
26,726✔
98
    }
99

100
    #[inline]
101
    fn peek_bits(&mut self, n_bits: usize) -> Result<u32, Self::Error> {
5,349✔
102
        if n_bits == 0 {
5,349✔
UNCOV
103
            return Ok(0);
×
104
        }
105

106
        #[cfg(feature = "checks")]
107
        assert!(n_bits <= 32);
10,698✔
108

109
        self.backend.set_word_pos(self.bit_index / 64)?;
16,047✔
110
        let in_word_offset = (self.bit_index % 64) as usize;
10,698✔
111

112
        let res = if (in_word_offset + n_bits) <= 64 {
10,698✔
113
            // single word access
114
            let word = self.backend.read_word()?.to_be();
19,388✔
115
            (word << in_word_offset) >> (64 - n_bits)
4,847✔
116
        } else {
117
            // double word access
118
            let high_word = self.backend.read_word()?.to_be();
2,008✔
119
            let low_word = self.backend.read_word()?.to_be();
2,008✔
120
            let shamt1 = 64 - n_bits;
1,004✔
121
            let shamt2 = 128 - in_word_offset - n_bits;
1,004✔
122
            ((high_word << in_word_offset) >> shamt1) | (low_word >> shamt2)
1,004✔
123
        };
124
        Ok(res as u32)
5,349✔
125
    }
126

127
    #[inline]
128
    fn read_unary(&mut self) -> Result<u64, Self::Error> {
6,330✔
129
        self.backend.set_word_pos(self.bit_index / 64)?;
18,990✔
130
        let in_word_offset = self.bit_index % 64;
12,660✔
131
        let mut bits_in_word = 64 - in_word_offset;
12,660✔
132
        let mut total = 0;
12,660✔
133

134
        let mut word = self.backend.read_word()?.to_be();
25,320✔
135
        word <<= in_word_offset;
6,330✔
136
        loop {
137
            let zeros = word.leading_zeros() as u64;
127,040✔
138
            // the unary code fits in the word
139
            if zeros < bits_in_word {
63,520✔
140
                self.bit_index += total + zeros + 1;
6,330✔
141
                return Ok(total + zeros);
6,330✔
142
            }
143
            total += bits_in_word;
57,190✔
144
            bits_in_word = 64;
57,190✔
145
            word = self.backend.read_word()?.to_be();
171,570✔
146
        }
147
    }
148

149
    #[inline(always)]
150
    fn skip_bits_after_peek(&mut self, n: usize) {
2,283✔
151
        self.bit_index += n as u64;
2,283✔
152
    }
153
}
154

155
impl<E: Endianness, WR: WordSeek, RP: ReadParams> BitSeek for BitReader<E, WR, RP> {
156
    type Error = Infallible;
157

158
    fn bit_pos(&mut self) -> Result<u64, Self::Error> {
64,076✔
159
        Ok(self.bit_index)
64,076✔
160
    }
161

162
    fn set_bit_pos(&mut self, bit_index: u64) -> Result<(), Self::Error> {
×
163
        self.bit_index = bit_index;
×
UNCOV
164
        Ok(())
×
165
    }
166
}
167

168
impl<WR: WordRead<Word = u64> + WordSeek<Error = <WR as WordRead>::Error>, RP: ReadParams>
169
    BitRead<LE> for BitReader<LE, WR, RP>
170
{
171
    type Error = <WR as WordRead>::Error;
172
    type PeekWord = u32;
173
    const PEEK_BITS: usize = 32;
174

175
    #[inline]
176
    fn skip_bits(&mut self, n_bits: usize) -> Result<(), Self::Error> {
×
177
        self.bit_index += n_bits as u64;
×
UNCOV
178
        Ok(())
×
179
    }
180

181
    #[inline]
182
    fn read_bits(&mut self, num_bits: usize) -> Result<u64, Self::Error> {
26,972✔
183
        #[cfg(feature = "checks")]
184
        assert!(num_bits <= 64);
53,944✔
185

186
        if num_bits == 0 {
26,972✔
187
            return Ok(0);
246✔
188
        }
189

190
        self.backend.set_word_pos(self.bit_index / 64)?;
80,178✔
191
        let in_word_offset = (self.bit_index % 64) as usize;
53,452✔
192

193
        let res = if (in_word_offset + num_bits) <= 64 {
53,452✔
194
            // single word access
195
            let word = self.backend.read_word()?.to_le();
83,676✔
196
            let shamt = 64 - num_bits;
41,838✔
197
            (word << (shamt - in_word_offset)) >> shamt
41,838✔
198
        } else {
199
            // double word access
200
            let low_word = self.backend.read_word()?.to_le();
23,228✔
201
            let high_word = self.backend.read_word()?.to_le();
23,228✔
202
            let shamt1 = 128 - in_word_offset - num_bits;
11,614✔
203
            let shamt2 = 64 - num_bits;
11,614✔
204
            ((high_word << shamt1) >> shamt2) | (low_word >> in_word_offset)
11,614✔
205
        };
206
        self.bit_index += num_bits as u64;
26,726✔
207
        Ok(res)
26,726✔
208
    }
209

210
    #[inline]
211
    fn peek_bits(&mut self, n_bits: usize) -> Result<u32, Self::Error> {
5,349✔
212
        if n_bits == 0 {
5,349✔
UNCOV
213
            return Ok(0);
×
214
        }
215

216
        #[cfg(feature = "checks")]
217
        assert!(n_bits <= 32);
10,698✔
218

219
        self.backend.set_word_pos(self.bit_index / 64)?;
16,047✔
220
        let in_word_offset = (self.bit_index % 64) as usize;
10,698✔
221

222
        let res = if (in_word_offset + n_bits) <= 64 {
10,698✔
223
            // single word access
224
            let word = self.backend.read_word()?.to_le();
19,388✔
225
            let shamt = 64 - n_bits;
9,694✔
226
            (word << (shamt - in_word_offset)) >> shamt
9,694✔
227
        } else {
228
            // double word access
229
            let low_word = self.backend.read_word()?.to_le();
2,008✔
230
            let high_word = self.backend.read_word()?.to_le();
2,008✔
231
            let shamt1 = 128 - in_word_offset - n_bits;
1,004✔
232
            let shamt2 = 64 - n_bits;
1,004✔
233
            ((high_word << shamt1) >> shamt2) | (low_word >> in_word_offset)
1,004✔
234
        };
235
        Ok(res as u32)
5,349✔
236
    }
237

238
    #[inline]
239
    fn read_unary(&mut self) -> Result<u64, Self::Error> {
6,330✔
240
        self.backend.set_word_pos(self.bit_index / 64)?;
18,990✔
241
        let in_word_offset = self.bit_index % 64;
12,660✔
242
        let mut bits_in_word = 64 - in_word_offset;
12,660✔
243
        let mut total = 0;
12,660✔
244

245
        let mut word = self.backend.read_word()?.to_le();
25,320✔
246
        word >>= in_word_offset;
6,330✔
247
        loop {
248
            let zeros = word.trailing_zeros() as u64;
127,040✔
249
            // the unary code fits in the word
250
            if zeros < bits_in_word {
63,520✔
251
                self.bit_index += total + zeros + 1;
6,330✔
252
                return Ok(total + zeros);
6,330✔
253
            }
254
            total += bits_in_word;
57,190✔
255
            bits_in_word = 64;
57,190✔
256
            word = self.backend.read_word()?.to_le();
171,570✔
257
        }
258
    }
259

260
    #[inline(always)]
261
    fn skip_bits_after_peek(&mut self, n: usize) {
2,283✔
262
        self.bit_index += n as u64;
2,283✔
263
    }
264
}
265

266
#[cfg(feature = "std")]
267
impl<WR: WordRead<Word = u64> + WordSeek<Error = <WR as WordRead>::Error>, RP: ReadParams>
268
    std::io::Read for BitReader<LE, WR, RP>
269
{
270
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
854✔
271
        let mut read = 0;
1,708✔
272
        let mut iter = buf.chunks_exact_mut(8);
2,562✔
273

274
        for chunk in &mut iter {
1,542✔
275
            match self.read_bits(64) {
1,376✔
276
                Ok(word) => {
1,376✔
277
                    chunk.copy_from_slice(&word.to_le_bytes());
2,064✔
278
                    read += 8;
688✔
279
                }
280
                // If we read some bytes, return them; the error will
281
                // resurface at the next call
UNCOV
282
                Err(_) if read > 0 => return Ok(read),
×
UNCOV
283
                Err(e) => {
×
UNCOV
284
                    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e));
×
285
                }
286
            }
287
        }
288

289
        let rem = iter.into_remainder();
2,562✔
290
        if !rem.is_empty() {
854✔
291
            match self.read_bits(rem.len() * 8) {
2,436✔
292
                Ok(word) => {
1,624✔
293
                    rem.copy_from_slice(&word.to_le_bytes()[..rem.len()]);
4,060✔
294
                    read += rem.len();
812✔
295
                }
UNCOV
296
                Err(_) if read > 0 => return Ok(read),
×
UNCOV
297
                Err(e) => {
×
UNCOV
298
                    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e));
×
299
                }
300
            }
301
        }
302

303
        Ok(read)
854✔
304
    }
305
}
306

307
#[cfg(feature = "std")]
308
impl<WR: WordRead<Word = u64> + WordSeek<Error = <WR as WordRead>::Error>, RP: ReadParams>
309
    std::io::Read for BitReader<BE, WR, RP>
310
{
311
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
854✔
312
        let mut read = 0;
1,708✔
313
        let mut iter = buf.chunks_exact_mut(8);
2,562✔
314

315
        for chunk in &mut iter {
1,542✔
316
            match self.read_bits(64) {
1,376✔
317
                Ok(word) => {
1,376✔
318
                    chunk.copy_from_slice(&word.to_be_bytes());
2,064✔
319
                    read += 8;
688✔
320
                }
321
                // If we read some bytes, return them; the error will
322
                // resurface at the next call
UNCOV
323
                Err(_) if read > 0 => return Ok(read),
×
UNCOV
324
                Err(e) => {
×
UNCOV
325
                    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e));
×
326
                }
327
            }
328
        }
329

330
        let rem = iter.into_remainder();
2,562✔
331
        if !rem.is_empty() {
854✔
332
            match self.read_bits(rem.len() * 8) {
2,436✔
333
                Ok(word) => {
1,624✔
334
                    rem.copy_from_slice(&word.to_be_bytes()[8 - rem.len()..]);
4,060✔
335
                    read += rem.len();
812✔
336
                }
UNCOV
337
                Err(_) if read > 0 => return Ok(read),
×
UNCOV
338
                Err(e) => {
×
UNCOV
339
                    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e));
×
340
                }
341
            }
342
        }
343

344
        Ok(read)
854✔
345
    }
346
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc