vortex_fastlanes/bitpacking/array/
mod.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::mem::MaybeUninit;
7
8use fastlanes::BitPacking;
9use vortex_array::ArrayRef;
10use vortex_array::ExecutionCtx;
11use vortex_array::TypedArrayRef;
12use vortex_array::array_slots;
13use vortex_array::arrays::Primitive;
14use vortex_array::arrays::PrimitiveArray;
15use vortex_array::buffer::BufferHandle;
16use vortex_array::dtype::DType;
17use vortex_array::dtype::NativePType;
18use vortex_array::dtype::PType;
19use vortex_array::patches::PatchSlotIndices;
20use vortex_array::patches::Patches;
21use vortex_array::patches::PatchesData;
22use vortex_array::validity::Validity;
23use vortex_array::vtable::child_to_validity;
24use vortex_error::VortexResult;
25use vortex_error::vortex_ensure;
26use vortex_error::vortex_err;
27
28pub mod bitpack_compress;
29pub mod bitpack_decompress;
30pub mod unpack_iter;
31
32use crate::BitPackedArray;
33use crate::FL_CHUNK_SIZE;
34use crate::bitpack_compress::bitpack_encode;
35use crate::unpack_iter::BitPacked as BitPackedIter;
36use crate::unpack_iter::BitUnpackedChunks;
37
38#[array_slots(crate::BitPacked)]
39pub struct BitPackedSlots {
40 #[slot(0)]
42 pub patch_indices: Option<ArrayRef>,
43 #[slot(1)]
45 pub patch_values: Option<ArrayRef>,
46 #[slot(2)]
48 pub patch_chunk_offsets: Option<ArrayRef>,
49 #[slot(3)]
51 pub validity_child: Option<ArrayRef>,
52}
53
54pub(crate) const PATCH_SLOTS: PatchSlotIndices = PatchSlotIndices {
55 indices: BitPackedSlots::PATCH_INDICES,
56 values: BitPackedSlots::PATCH_VALUES,
57 chunk_offsets: BitPackedSlots::PATCH_CHUNK_OFFSETS,
58};
59
60pub struct BitPackedDataParts {
61 pub offset: u16,
62 pub bit_width: u8,
63 pub len: usize,
64 pub packed: BufferHandle,
65 pub patches: Option<Patches>,
66 pub validity: Validity,
67}
68
69#[derive(Clone, Debug)]
70pub struct BitPackedData {
71 pub(super) offset: u16,
74 pub(super) bit_width: u8,
75 pub(super) packed: BufferHandle,
76 pub(super) patches_data: Option<PatchesData>,
78}
79
80impl Display for BitPackedData {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 write!(f, "bit_width: {}, offset: {}", self.bit_width, self.offset)
83 }
84}
85
86impl BitPackedData {
87 pub fn try_new(
129 packed: BufferHandle,
130 patches: Option<Patches>,
131 bit_width: u8,
132 offset: u16,
133 ) -> VortexResult<Self> {
134 vortex_ensure!(bit_width <= 64, "Unsupported bit width {bit_width}");
135 vortex_ensure!(
136 offset < 1024,
137 "Offset must be less than the full block i.e., 1024, got {offset}"
138 );
139
140 Ok(Self {
141 offset,
142 bit_width,
143 packed,
144 patches_data: patches.as_ref().map(PatchesData::from_patches),
145 })
146 }
147
148 pub(crate) fn validate(
149 packed: &BufferHandle,
150 ptype: PType,
151 validity: &Validity,
152 patches: Option<&Patches>,
153 bit_width: u8,
154 length: usize,
155 offset: u16,
156 ) -> VortexResult<()> {
157 vortex_ensure!(ptype.is_int(), MismatchedTypes: "integer", ptype);
158 vortex_ensure!(bit_width <= 64, "Unsupported bit width {bit_width}");
159
160 if let Some(validity_len) = validity.maybe_len() {
161 vortex_ensure!(
162 validity_len == length,
163 "BitPackedArray validity length {validity_len} != array length {length}",
164 );
165 }
166
167 if let Some(patches) = patches {
169 Self::validate_patches(patches, ptype, length)?;
170 }
171
172 let expected_packed_len =
174 (length + offset as usize).div_ceil(1024) * (128 * bit_width as usize);
175 vortex_ensure!(
176 packed.len() == expected_packed_len,
177 "Expected {} packed bytes, got {}",
178 expected_packed_len,
179 packed.len()
180 );
181
182 Ok(())
183 }
184
185 fn validate_patches(patches: &Patches, ptype: PType, len: usize) -> VortexResult<()> {
186 vortex_ensure!(
188 patches.dtype().eq_ignore_nullability(ptype.into()),
189 "Patches DType {} does not match BitPackedArray dtype {}",
190 patches.dtype().as_nonnullable(),
191 ptype
192 );
193
194 vortex_ensure!(
195 patches.array_len() == len,
196 "BitPackedArray patches length {} != expected {len}",
197 patches.array_len(),
198 );
199
200 Ok(())
201 }
202
203 pub fn ptype(&self, dtype: &DType) -> PType {
204 dtype.as_ptype()
205 }
206
207 #[inline]
209 pub fn packed(&self) -> &BufferHandle {
210 &self.packed
211 }
212
213 #[inline]
215 pub fn packed_slice<T: NativePType + BitPacking>(&self) -> &[T] {
216 let packed_bytes = self.packed().as_host();
217 let packed_ptr: *const T = packed_bytes.as_ptr().cast();
218 let packed_len = packed_bytes.len() / size_of::<T>();
220
221 unsafe { std::slice::from_raw_parts(packed_ptr, packed_len) }
225 }
226
227 pub fn unpacked_chunks<'a, T: BitPackedIter>(
229 &'a self,
230 dtype: &DType,
231 len: usize,
232 scratch: &'a mut [MaybeUninit<T>; FL_CHUNK_SIZE],
233 ) -> VortexResult<BitUnpackedChunks<'a, T>> {
234 assert_eq!(
235 T::PTYPE,
236 self.ptype(dtype),
237 "Requested type doesn't match the array ptype"
238 );
239 BitUnpackedChunks::try_new(self, len, scratch)
240 }
241
242 #[inline]
244 pub fn bit_width(&self) -> u8 {
245 self.bit_width
246 }
247
248 #[inline]
249 pub fn offset(&self) -> u16 {
250 self.offset
251 }
252
253 pub fn encode(
265 array: &ArrayRef,
266 bit_width: u8,
267 ctx: &mut ExecutionCtx,
268 ) -> VortexResult<BitPackedArray> {
269 let parray: PrimitiveArray = array
270 .clone()
271 .try_downcast::<Primitive>()
272 .map_err(|a| vortex_err!(InvalidArgument: "Bitpacking can only encode primitive arrays, got {}", a.encoding_id()))?;
273 bitpack_encode(&parray, bit_width, None, ctx)
274 }
275
276 #[inline]
280 pub fn max_packed_value(&self) -> usize {
281 (1 << self.bit_width()) - 1
282 }
283}
284
285pub trait BitPackedArrayExt: BitPackedArraySlotsExt {
286 #[inline]
287 fn packed(&self) -> &BufferHandle {
288 BitPackedData::packed(self)
289 }
290
291 #[inline]
292 fn bit_width(&self) -> u8 {
293 BitPackedData::bit_width(self)
294 }
295
296 #[inline]
297 fn offset(&self) -> u16 {
298 BitPackedData::offset(self)
299 }
300
301 #[inline]
302 fn patches(&self) -> Option<Patches> {
303 PatchesData::patches_from_slots(
304 self.patches_data.as_ref(),
305 self.as_ref().len(),
306 self.as_ref().slots(),
307 PATCH_SLOTS,
308 )
309 }
310
311 #[inline]
312 fn validity(&self) -> Validity {
313 child_to_validity(self.validity_child(), self.as_ref().dtype().nullability())
314 }
315
316 #[inline]
317 fn packed_slice<T: NativePType + BitPacking>(&self) -> &[T] {
318 BitPackedData::packed_slice::<T>(self)
319 }
320
321 #[inline]
322 fn unpacked_chunks<'a, T: BitPackedIter>(
323 &'a self,
324 scratch: &'a mut [MaybeUninit<T>; FL_CHUNK_SIZE],
325 ) -> VortexResult<BitUnpackedChunks<'a, T>> {
326 BitPackedData::unpacked_chunks::<T>(
327 self,
328 self.as_ref().dtype(),
329 self.as_ref().len(),
330 scratch,
331 )
332 }
333}
334
335impl<T: TypedArrayRef<crate::BitPacked>> BitPackedArrayExt for T {}
336
337#[cfg(test)]
338mod test {
339 use std::sync::LazyLock;
340
341 use vortex_array::IntoArray;
342 use vortex_array::VortexSessionExecute;
343 use vortex_array::arrays::PrimitiveArray;
344 use vortex_array::assert_arrays_eq;
345 use vortex_buffer::Buffer;
346 use vortex_session::VortexSession;
347
348 use crate::BitPackedData;
349 use crate::bitpacking::array::BitPackedArrayExt;
350
351 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
352 let session = vortex_array::array_session();
353 crate::initialize(&session);
354 session
355 });
356
357 #[test]
358 fn test_encode() {
359 let mut ctx = SESSION.create_execution_ctx();
360 let values = [
361 Some(1u64),
362 None,
363 Some(1),
364 None,
365 Some(1),
366 None,
367 Some(u64::MAX),
368 ];
369 let uncompressed = PrimitiveArray::from_option_iter(values);
370 let packed = BitPackedData::encode(&uncompressed.into_array(), 1, &mut ctx).unwrap();
371 let expected = PrimitiveArray::from_option_iter(values);
372 let packed_primitive = packed
373 .as_array()
374 .clone()
375 .execute::<PrimitiveArray>(&mut ctx)
376 .unwrap();
377 assert_arrays_eq!(packed_primitive, expected, &mut ctx);
378 }
379
380 #[test]
381 fn test_encode_too_wide() {
382 let mut ctx = SESSION.create_execution_ctx();
383 let values = [Some(1u8), None, Some(1), None, Some(1), None];
384 let uncompressed = PrimitiveArray::from_option_iter(values);
385 let _packed = BitPackedData::encode(&uncompressed.clone().into_array(), 8, &mut ctx)
386 .expect_err("Cannot pack value into the same width");
387 let _packed = BitPackedData::encode(&uncompressed.into_array(), 9, &mut ctx)
388 .expect_err("Cannot pack value into larger width");
389 }
390
391 #[test]
392 fn signed_with_patches() {
393 let mut ctx = SESSION.create_execution_ctx();
394 let values: Buffer<i32> = (0i32..=512).collect();
395 let parray = values.clone().into_array();
396
397 let packed_with_patches = BitPackedData::encode(&parray, 9, &mut ctx).unwrap();
398 assert!(packed_with_patches.patches().is_some());
399 let packed_primitive = packed_with_patches
400 .as_array()
401 .clone()
402 .execute::<PrimitiveArray>(&mut ctx)
403 .unwrap();
404 assert_arrays_eq!(
405 packed_primitive,
406 PrimitiveArray::new(values, vortex_array::validity::Validity::NonNullable),
407 &mut ctx
408 );
409 }
410}