From 3a8c077b46066f3195bc029ba21a929c9bbc7639 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Thu, 23 Jul 2026 07:13:52 +0000 Subject: [PATCH] fix(bool): trim oversized backing buffer views Bool arrays constructed from sliced bitmaps can retain buffer views that extend beyond their logical byte range. Serialization writes the exposed buffers in full, which can unnecessarily increase file size and retain logically excluded suffix data. This affects the release path of `BoolArray::new_unchecked` and callers that provide an oversized `BufferHandle`. Normalize unchecked host bit buffers with `shrink_offset`, and trim oversized host or device buffer handles to the byte range required by the Bool metadata. Both operations create zero-copy views and preserve the underlying allocation, device residency, alignment, and ownership model. No public API signatures are changed. Bool array constructors now expose only the backing bytes required by their logical offset and length. Signed-off-by: jiaqizho --- vortex-array/src/arrays/bool/array.rs | 43 ++++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/vortex-array/src/arrays/bool/array.rs b/vortex-array/src/arrays/bool/array.rs index 9a0258fd3d7..7fdbe5595d1 100644 --- a/vortex-array/src/arrays/bool/array.rs +++ b/vortex-array/src/arrays/bool/array.rs @@ -285,22 +285,28 @@ impl BoolData { bits.len() * 8, ); - Ok(Self { - bits, - meta: BitBufferMeta::new(offset, len), - }) + let meta = BitBufferMeta::new(offset, len); + let bits = if bits.len() == meta.byte_len() { + bits + } else { + bits.slice(0..meta.byte_len()) + }; + + Ok(Self { bits, meta }) } pub(super) unsafe fn new_unchecked(bits: BitBuffer, validity: Validity) -> Self { + let bits = bits.shrink_offset(); + if cfg!(debug_assertions) { - Self::try_new(bits, validity).vortex_expect("Failed to create BoolData") - } else { - let (offset, len, buffer) = bits.into_inner(); + Self::validate(&bits, &validity).vortex_expect("Failed to create BoolData"); + } - Self { - bits: BufferHandle::new_host(buffer), - meta: BitBufferMeta::new(offset, len), - } + let (offset, len, buffer) = bits.into_inner(); + + Self { + bits: BufferHandle::new_host(buffer), + meta: BitBufferMeta::new(offset, len), } } @@ -369,6 +375,7 @@ mod tests { use vortex_buffer::Alignment; use vortex_buffer::BitBuffer; + use vortex_buffer::BitBufferMeta; use vortex_buffer::BitBufferMut; use vortex_buffer::ByteBuffer; use vortex_buffer::buffer; @@ -494,6 +501,20 @@ mod tests { Ok(()) } + #[test] + fn constructors_trim_oversized_backing_buffer() { + let bits = BitBuffer::new_set(8192).slice(3..11); + let expected_nbytes = BitBufferMeta::new(bits.offset(), bits.len()).byte_len() as u64; + + let handle = BufferHandle::new_host(bits.inner().clone()); + let from_handle = + BoolArray::new_handle(handle, bits.offset(), bits.len(), Validity::NonNullable); + let unchecked = unsafe { BoolArray::new_unchecked(bits, Validity::NonNullable) }; + + assert_eq!(from_handle.nbytes(), expected_nbytes); + assert_eq!(unchecked.nbytes(), expected_nbytes); + } + #[test] fn patch_bools_owned() { let mut ctx = array_session().create_execution_ctx();