vortex_fastlanes/bitpacking/array/
unpack_iter.rs1use std::mem;
5use std::mem::MaybeUninit;
6use std::ops::Range;
7
8use fastlanes::BitPacking;
9use lending_iterator::gat;
10use lending_iterator::prelude::Item;
11#[gat(Item)]
12use lending_iterator::prelude::LendingIterator;
13use vortex_array::dtype::PhysicalPType;
14use vortex_error::VortexResult;
15use vortex_error::vortex_ensure;
16
17use crate::BitPackedData;
18use crate::FL_CHUNK_SIZE;
19
20const CHUNK_SIZE: usize = FL_CHUNK_SIZE;
21
22pub trait UnpackStrategy<T: PhysicalPType> {
24 unsafe fn unpack_chunk(&self, bit_width: usize, chunk: &[T::Physical], dst: &mut [T::Physical]);
30}
31
32pub struct BitPackingStrategy;
34
35impl<T: PhysicalPType<Physical: BitPacking>> UnpackStrategy<T> for BitPackingStrategy {
36 #[allow(clippy::inline_always)]
37 #[inline(always)]
38 unsafe fn unpack_chunk(
39 &self,
40 bit_width: usize,
41 chunk: &[T::Physical],
42 dst: &mut [T::Physical],
43 ) {
44 unsafe {
46 BitPacking::unchecked_unpack(bit_width, chunk, dst);
47 }
48 }
49}
50
51pub struct UnpackedChunks<'a, T: PhysicalPType, S: UnpackStrategy<T>> {
90 strategy: S,
91 bit_width: usize,
92 offset: usize,
93 len: usize,
94 num_chunks: usize,
95 last_chunk_length: usize,
97 packed: &'a [T::Physical],
98 scratch: &'a mut [MaybeUninit<T>; CHUNK_SIZE],
99}
100
101pub type BitUnpackedChunks<'a, T> = UnpackedChunks<'a, T, BitPackingStrategy>;
102
103impl<'a, T: BitPacked> BitUnpackedChunks<'a, T> {
104 pub fn try_new(
105 array: &'a BitPackedData,
106 len: usize,
107 scratch: &'a mut [MaybeUninit<T>; CHUNK_SIZE],
108 ) -> VortexResult<Self> {
109 Self::try_new_with_strategy(
110 BitPackingStrategy,
111 array.packed_slice::<T::Physical>(),
112 array.bit_width() as usize,
113 array.offset() as usize,
114 len,
115 scratch,
116 )
117 }
118
119 pub fn full_chunks(&mut self) -> BitUnpackIterator<'_, T> {
120 let elems_per_chunk = self.elems_per_chunk();
121 let last_chunk_is_sliced = self.last_chunk_is_sliced() as usize;
122 let first_chunk_is_sliced = self.first_chunk_is_sliced();
123 BitUnpackIterator::new(
124 self.packed,
125 self.scratch,
126 self.bit_width,
127 elems_per_chunk,
128 self.num_chunks - last_chunk_is_sliced,
129 first_chunk_is_sliced,
130 )
131 }
132}
133
134impl<'a, T: PhysicalPType, S: UnpackStrategy<T>> UnpackedChunks<'a, T, S> {
135 pub fn try_new_with_strategy(
136 strategy: S,
137 packed: &'a [T::Physical],
138 bit_width: usize,
139 offset: usize,
140 len: usize,
141 scratch: &'a mut [MaybeUninit<T>; CHUNK_SIZE],
142 ) -> VortexResult<Self> {
143 let (num_chunks, last_chunk_length) =
144 validate_packed::<T>(packed.len(), bit_width, offset, len)?;
145 Ok(Self {
146 strategy,
147 bit_width,
148 offset,
149 len,
150 num_chunks,
151 last_chunk_length,
152 packed,
153 scratch,
154 })
155 }
156
157 #[allow(clippy::inline_always)]
158 #[inline(always)]
159 const fn elems_per_chunk(&self) -> usize {
160 128 * self.bit_width / size_of::<T>()
161 }
162
163 pub fn initial(&mut self) -> Option<&mut [T]> {
165 (self.first_chunk_is_sliced() || self.num_chunks == 1).then(|| {
166 let chunk: &[T::Physical] = &self.packed[..self.elems_per_chunk()];
167 let dst: &mut [MaybeUninit<T>] = self.scratch;
168 let dst: &mut [T::Physical] = unsafe { mem::transmute(dst) };
169
170 let header_end_slice = if self.num_chunks == 1 {
171 self.len
172 } else {
173 CHUNK_SIZE - self.offset
174 };
175 unsafe {
179 self.strategy.unpack_chunk(self.bit_width, chunk, dst);
180 mem::transmute(&mut self.scratch[self.offset..][..header_end_slice])
181 }
182 })
183 }
184
185 pub fn decode_into(&mut self, output: &mut [MaybeUninit<T>]) {
187 debug_assert_eq!(output.len(), self.len);
188 let mut local_idx = 0;
189
190 if let Some(initial) = self.initial() {
191 local_idx = initial.len();
192
193 let init_initial: &[MaybeUninit<T>] = unsafe { mem::transmute(initial) };
196 output[..local_idx].copy_from_slice(init_initial);
197 }
198
199 local_idx = self.decode_full_chunks_into_at(output, local_idx);
200
201 if let Some(trailer) = self.trailer() {
202 let init_trailer: &[MaybeUninit<T>] = unsafe { mem::transmute(trailer) };
205 output[local_idx..][..init_trailer.len()].copy_from_slice(init_trailer);
206 local_idx += init_trailer.len();
207 }
208
209 debug_assert_eq!(local_idx, self.len);
210 }
211
212 pub(crate) fn decode_map_into<U>(
214 &mut self,
215 output: &mut [MaybeUninit<U>],
216 mut f: impl FnMut(T) -> U,
217 ) {
218 debug_assert_eq!(output.len(), self.len);
219
220 self.for_each_unpacked_chunk(|chunk, range| {
221 write_map(chunk, &mut output[range], &mut f);
222 });
223 }
224
225 pub(crate) fn for_each_unpacked_chunk<F>(&mut self, mut f: F)
227 where
228 F: FnMut(&mut [T], Range<usize>),
229 {
230 let mut local_idx = 0;
231
232 if let Some(initial) = self.initial() {
233 let chunk_len = initial.len();
234 f(initial, local_idx..local_idx + chunk_len);
235 local_idx += chunk_len;
236 }
237
238 if self.num_chunks > 1 {
239 let packed_slice = self.packed;
240 let elems_per_chunk = self.elems_per_chunk();
241 for i in self.full_chunks_range() {
242 let chunk = &packed_slice[i * elems_per_chunk..][..elems_per_chunk];
243 unsafe {
244 let dst: &mut [T::Physical] = mem::transmute(&mut self.scratch[..]);
245 self.strategy.unpack_chunk(self.bit_width, chunk, dst);
246 let unpacked: &mut [T] = mem::transmute(&mut self.scratch[..]);
247 f(unpacked, local_idx..local_idx + CHUNK_SIZE);
248 }
249 local_idx += CHUNK_SIZE;
250 }
251 }
252
253 if let Some(trailer) = self.trailer() {
254 let chunk_len = trailer.len();
255 f(trailer, local_idx..local_idx + chunk_len);
256 local_idx += chunk_len;
257 }
258
259 debug_assert_eq!(local_idx, self.len);
260 }
261
262 fn decode_full_chunks_into_at(
264 &mut self,
265 output: &mut [MaybeUninit<T>],
266 start_idx: usize,
267 ) -> usize {
268 if self.num_chunks == 1 {
269 return start_idx;
270 }
271
272 let mut local_idx = start_idx;
273
274 let packed_slice = self.packed;
275 let elems_per_chunk = self.elems_per_chunk();
276 for i in self.full_chunks_range() {
277 let chunk = &packed_slice[i * elems_per_chunk..][..elems_per_chunk];
278
279 unsafe {
280 let uninit_dst = &mut output[local_idx..local_idx + CHUNK_SIZE];
281 let dst: &mut [T::Physical] = mem::transmute(uninit_dst);
283 self.strategy.unpack_chunk(self.bit_width, chunk, dst);
284 }
285 local_idx += CHUNK_SIZE;
286 }
287 local_idx
288 }
289
290 fn full_chunks_range(&self) -> Range<usize> {
291 (self.first_chunk_is_sliced() as usize)
292 ..(self.num_chunks - self.last_chunk_is_sliced() as usize)
293 }
294
295 pub fn trailer(&mut self) -> Option<&mut [T]> {
297 (self.last_chunk_is_sliced() && self.num_chunks > 1).then(|| {
298 let chunk: &[T::Physical] = &self.packed
299 [(self.num_chunks - 1) * self.elems_per_chunk()..][..self.elems_per_chunk()];
300 let dst: &mut [MaybeUninit<T>] = self.scratch;
301 let dst: &mut [T::Physical] = unsafe { mem::transmute(dst) };
302 unsafe {
306 self.strategy.unpack_chunk(self.bit_width, chunk, dst);
307 mem::transmute(&mut self.scratch[..self.last_chunk_length])
308 }
309 })
310 }
311
312 fn last_chunk_is_sliced(&self) -> bool {
313 self.last_chunk_length != 0
314 }
315
316 fn first_chunk_is_sliced(&self) -> bool {
317 self.offset != 0
318 }
319}
320
321pub(crate) fn for_each_packed_chunk<T, F>(
323 packed: &[T::Physical],
324 bit_width: usize,
325 offset: usize,
326 len: usize,
327 mut f: F,
328) -> VortexResult<()>
329where
330 T: PhysicalPType,
331 F: FnMut(&[T::Physical], Range<usize>),
332{
333 let (num_chunks, _) = validate_packed::<T>(packed.len(), bit_width, offset, len)?;
334 let elems_per_chunk = 128 * bit_width / size_of::<T>();
335 let padded_len = offset + len;
336 for chunk in 0..num_chunks {
337 let packed_chunk = &packed[chunk * elems_per_chunk..][..elems_per_chunk];
338 let start = chunk * CHUNK_SIZE;
339 let end = (start + CHUNK_SIZE).min(padded_len);
340 f(packed_chunk, start..end);
341 }
342 Ok(())
343}
344
345fn validate_packed<T: PhysicalPType>(
346 packed_len: usize,
347 bit_width: usize,
348 offset: usize,
349 len: usize,
350) -> VortexResult<(usize, usize)> {
351 vortex_ensure!(
352 offset < CHUNK_SIZE,
353 "Invalid bit-packed offset {offset}, expected < {CHUNK_SIZE}"
354 );
355 let elems_per_chunk = 128 * bit_width / size_of::<T>();
356 let num_chunks = (offset + len).div_ceil(CHUNK_SIZE);
357 vortex_ensure!(
358 packed_len == num_chunks * elems_per_chunk,
359 "Invalid packed length: got {packed_len}, expected {}",
360 num_chunks * elems_per_chunk
361 );
362 Ok((num_chunks, (offset + len) % CHUNK_SIZE))
363}
364
365pub struct BitUnpackIterator<'a, T: BitPacked + 'a> {
367 packed: &'a [T::Physical],
368 buffer: &'a mut [MaybeUninit<T>; CHUNK_SIZE],
369 bit_width: usize,
370 elems_per_chunk: usize,
371 num_chunks: usize,
372 idx: usize,
373}
374
375impl<'a, T: BitPacked> BitUnpackIterator<'a, T> {
376 pub fn new(
377 packed: &'a [T::Physical],
378 buffer: &'a mut [MaybeUninit<T>; CHUNK_SIZE],
379 bit_width: usize,
380 elems_per_chunk: usize,
381 num_chunks: usize,
382 first_chunk_is_sliced: bool,
383 ) -> Self {
384 Self {
385 packed,
386 buffer,
387 bit_width,
388 elems_per_chunk,
389 num_chunks,
390 idx: if first_chunk_is_sliced { 1 } else { 0 },
391 }
392 }
393}
394
395#[gat]
396impl<'a, T: BitPacked + 'a> LendingIterator for BitUnpackIterator<'a, T> {
397 type Item<'next>
398 where
399 Self: 'next,
400 = &'next mut [T; CHUNK_SIZE];
401
402 fn next(&'_ mut self) -> Option<Item<'_, Self>> {
403 if self.idx >= self.num_chunks {
404 return None;
405 }
406
407 let chunk = &self.packed[self.idx * self.elems_per_chunk..][..self.elems_per_chunk];
408
409 let dst: &mut [MaybeUninit<T>] = self.buffer;
410 unsafe {
411 let dst: &mut [T::Physical] = mem::transmute(dst);
412
413 BitPacking::unchecked_unpack(self.bit_width, chunk, dst);
414 }
415 self.idx += 1;
416 Some(unsafe { mem::transmute::<&mut [MaybeUninit<T>; 1024], &mut [T; 1024]>(self.buffer) })
418 }
419}
420
421fn write_map<T: Copy, U>(src: &[T], dst: &mut [MaybeUninit<U>], f: &mut impl FnMut(T) -> U) {
422 for (dst, &src) in dst.iter_mut().zip(src.iter()) {
423 dst.write(f(src));
424 }
425}
426
427pub trait BitPacked: PhysicalPType<Physical: BitPacking> {}
428
429impl BitPacked for i8 {}
430impl BitPacked for i16 {}
431impl BitPacked for i32 {}
432impl BitPacked for i64 {}
433impl BitPacked for u8 {}
434impl BitPacked for u16 {}
435impl BitPacked for u32 {}
436impl BitPacked for u64 {}