|
3 | 3 |
|
4 | 4 | use std::ops::Not; |
5 | 5 |
|
6 | | -use bitvec::view::BitView; |
| 6 | +use arrow_buffer::bit_mask::set_bits; |
7 | 7 |
|
8 | 8 | use crate::BitBuffer; |
9 | 9 | use crate::BufferMut; |
@@ -487,24 +487,32 @@ impl BitBufferMut { |
487 | 487 | let end_bit_pos = start_bit_pos + bit_len; |
488 | 488 | let required_bytes = end_bit_pos.div_ceil(8); |
489 | 489 |
|
| 490 | + // `set_bits` below ORs into the destination, so stale bits past `len` in the existing |
| 491 | + // bytes (e.g. after `truncate` or `clear`) must be cleared first. Bytes appended below |
| 492 | + // are already zeroed. |
| 493 | + let existing_bits = self.buffer.len() * 8; |
| 494 | + if existing_bits > start_bit_pos { |
| 495 | + fill_bits( |
| 496 | + self.buffer.as_mut_slice(), |
| 497 | + start_bit_pos, |
| 498 | + existing_bits.min(end_bit_pos), |
| 499 | + false, |
| 500 | + ); |
| 501 | + } |
| 502 | + |
490 | 503 | // Ensure buffer has enough bytes |
491 | 504 | if required_bytes > self.buffer.len() { |
492 | 505 | self.buffer.push_n(0x00, required_bytes - self.buffer.len()); |
493 | 506 | } |
494 | 507 |
|
495 | | - // Use bitvec for efficient bit copying |
496 | | - let self_slice = self |
497 | | - .buffer |
498 | | - .as_mut_slice() |
499 | | - .view_bits_mut::<bitvec::prelude::Lsb0>(); |
500 | | - let other_slice = buffer |
501 | | - .inner() |
502 | | - .as_slice() |
503 | | - .view_bits::<bitvec::prelude::Lsb0>(); |
504 | | - |
505 | | - // Copy from source buffer (accounting for its offset) to destination (accounting for our offset + len) |
506 | | - let source_range = buffer.offset()..buffer.offset() + bit_len; |
507 | | - self_slice[start_bit_pos..end_bit_pos].copy_from_bitslice(&other_slice[source_range]); |
| 508 | + // Word-wise bit copy that handles mismatched source/destination bit offsets. |
| 509 | + set_bits( |
| 510 | + self.buffer.as_mut_slice(), |
| 511 | + buffer.inner().as_slice(), |
| 512 | + start_bit_pos, |
| 513 | + buffer.offset(), |
| 514 | + bit_len, |
| 515 | + ); |
508 | 516 |
|
509 | 517 | self.len += bit_len; |
510 | 518 | } |
@@ -879,7 +887,43 @@ mod tests { |
879 | 887 | assert!(frozen.value(7)); |
880 | 888 | } |
881 | 889 |
|
882 | | - #[cfg_attr(miri, ignore)] // bitvec crate uses a ptr cast that Miri doesn't support |
| 890 | + #[test] |
| 891 | + fn test_append_buffer_after_truncate() { |
| 892 | + // Truncating leaves stale set bits in the last partial byte; an append after that |
| 893 | + // must overwrite them rather than OR into them. |
| 894 | + let mut buf = BitBufferMut::new_set(16); |
| 895 | + buf.truncate(3); |
| 896 | + buf.append_buffer(&crate::BitBuffer::new_unset(8)); |
| 897 | + |
| 898 | + let frozen = buf.freeze(); |
| 899 | + assert_eq!(frozen.len(), 11); |
| 900 | + for i in 0..3 { |
| 901 | + assert!(frozen.value(i), "bit {i} should be set"); |
| 902 | + } |
| 903 | + for i in 3..11 { |
| 904 | + assert!(!frozen.value(i), "bit {i} should be unset"); |
| 905 | + } |
| 906 | + } |
| 907 | + |
| 908 | + #[test] |
| 909 | + fn test_append_buffer_misaligned_long() { |
| 910 | + // Force mismatched source/destination bit offsets across many words. |
| 911 | + let source = crate::BitBuffer::from_iter((0..301).map(|i| i % 3 == 0)); |
| 912 | + let source = source.slice(5..301); |
| 913 | + |
| 914 | + let mut dest = BitBufferMut::with_capacity(512); |
| 915 | + dest.append_n(true, 3); |
| 916 | + dest.append_buffer(&source); |
| 917 | + |
| 918 | + assert_eq!(dest.len(), 3 + source.len()); |
| 919 | + for i in 0..3 { |
| 920 | + assert!(dest.value(i), "prefix bit {i}"); |
| 921 | + } |
| 922 | + for i in 0..source.len() { |
| 923 | + assert_eq!(dest.value(3 + i), source.value(i), "bit {i}"); |
| 924 | + } |
| 925 | + } |
| 926 | + |
883 | 927 | #[test] |
884 | 928 | fn test_append_buffer_with_offsets() { |
885 | 929 | // Create source buffer with offset |
|
0 commit comments