use std::mem;
use std::mem::MaybeUninit;
use vortex_buffer::Alignment;
use vortex_buffer::BitBuffer;
use vortex_buffer::BufferMut;
use vortex_buffer::ByteBuffer;
use vortex_error::VortexExpect;
use crate::FL_CHUNK_SIZE;
pub fn transpose_bitbuffer(bits: BitBuffer) -> BitBuffer {
bits_op(bits, fastlanes::transpose_bits::<u64>)
}
pub fn untranspose_bitbuffer(bits: BitBuffer) -> BitBuffer {
assert!(
bits.len().is_multiple_of(FL_CHUNK_SIZE),
"Transposed BitBuffer length must be a multiple of {FL_CHUNK_SIZE}"
);
bits_op(bits, fastlanes::untranspose_bits)
}
fn bits_op<F: Fn(&[u64; 16], &mut [u64; 16])>(bits: BitBuffer, op: F) -> BitBuffer {
let bits = bits.sliced();
let (_offset, len, bytes) = bits.into_inner();
if len.is_multiple_of(FL_CHUNK_SIZE) && bytes.is_aligned(Alignment::of::<u64>()) {
match bytes.try_into_mut() {
Ok(mut bytes_mut) => {
let (chunks, _) = bytes_mut.as_chunks_mut::<128>();
let mut tmp = [0u64; 16];
for chunk in chunks {
let chunk_u64 =
unsafe { mem::transmute::<&mut [u8; 128], &mut [u64; 16]>(chunk) };
op(chunk_u64, &mut tmp);
chunk_u64.copy_from_slice(&tmp);
}
BitBuffer::new(bytes_mut.freeze().into_byte_buffer(), len)
}
Err(bytes) => bits_op_with_copy(bytes, len, op),
}
} else {
bits_op_with_copy(bytes, len, op)
}
}
fn bits_op_with_copy<F: Fn(&[u64; 16], &mut [u64; 16])>(
bytes: ByteBuffer,
len: usize,
op: F,
) -> BitBuffer {
let output_len = bytes.len().div_ceil(8).next_multiple_of(16);
let mut output = BufferMut::<u64>::with_capacity(output_len);
let (input_chunks, input_trailer) = bytes.as_chunks::<128>();
let (output_chunks, _) = unsafe {
mem::transmute::<&mut [MaybeUninit<u64>], &mut [u64]>(
&mut output.spare_capacity_mut()[..output_len],
)
}
.as_chunks_mut::<16>();
for (input, output) in input_chunks.iter().zip(output_chunks.iter_mut()) {
op(&load_chunk_unaligned(input), output);
}
if !input_trailer.is_empty() {
let mut padded_input = [0u8; 128];
padded_input[0..input_trailer.len()].clone_from_slice(input_trailer);
op(
&load_chunk_unaligned(&padded_input),
output_chunks
.last_mut()
.vortex_expect("Output wasn't a multiple of 128 bytes"),
);
}
unsafe { output.set_len(output_len) };
BitBuffer::new(
output.freeze().into_byte_buffer(),
len.next_multiple_of(FL_CHUNK_SIZE),
)
}
#[allow(clippy::host_endian_bytes)]
fn load_chunk_unaligned(chunk: &[u8; 128]) -> [u64; 16] {
let mut words = [0u64; 16];
let (bytes, _) = chunk.as_chunks::<8>();
for (word, bytes) in words.iter_mut().zip(bytes) {
*word = u64::from_ne_bytes(*bytes);
}
words
}
#[cfg(test)]
mod tests {
use vortex_buffer::BitBuffer;
use vortex_buffer::BitBufferMut;
use vortex_buffer::ByteBuffer;
use vortex_buffer::ByteBufferMut;
use super::*;
fn make_validity_bits(num_bits: usize) -> BitBuffer {
let mut builder = BitBufferMut::with_capacity(num_bits);
for i in 0..num_bits {
builder.append(i % 3 != 0);
}
builder.freeze()
}
fn force_copy_path(bits: BitBuffer) -> (BitBuffer, ByteBuffer) {
let (offset, len, bytes) = bits.into_inner();
let extra_ref = bytes.clone();
(BitBuffer::new_with_offset(bytes, len, offset), extra_ref)
}
#[test]
fn transpose_padding_copy_produces_same_bits() {
let bits = make_validity_bits(500);
let transposed = transpose_bitbuffer(bits.clone());
assert_eq!(transposed.len(), 1024);
let untransposed = untranspose_bitbuffer(transposed);
assert_eq!(untransposed.slice(0..500), bits)
}
#[test]
fn transpose_inplace_and_copy_produce_same_bits() {
let bits = make_validity_bits(2048);
let inplace_result = transpose_bitbuffer(bits.clone());
let (bits_shared, _hold) = force_copy_path(bits);
let copy_result = transpose_bitbuffer(bits_shared);
assert_eq!(inplace_result.len(), copy_result.len());
assert_eq!(inplace_result, copy_result);
}
#[test]
fn transpose_bitbuffer_roundtrip_non_aligned() {
let original_len = 1500;
let bits = make_validity_bits(original_len);
let transposed = transpose_bitbuffer(bits.clone());
let roundtripped = untranspose_bitbuffer(transposed);
assert_eq!(bits, roundtripped.slice(0..original_len));
}
#[test]
fn transpose_copy_path_survives_overallocation() {
for original_len in [129, 500, 1500, 9999] {
let bits = make_validity_bits(original_len);
for _ in 0..64 {
let transposed = transpose_bitbuffer(bits.clone());
let roundtripped = untranspose_bitbuffer(transposed);
assert_eq!(
roundtripped.slice(0..original_len),
bits,
"len={original_len}"
);
}
}
}
#[test]
fn untranspose_unaligned_buffer() {
let bits = make_validity_bits(2048);
let transposed = transpose_bitbuffer(bits.clone());
let expected = untranspose_bitbuffer(transposed.clone());
let (_, _, transposed_bytes) = transposed.sliced().into_inner();
let mut shifted = ByteBufferMut::with_capacity(transposed_bytes.len() + 1);
shifted.push(0xFF);
shifted.extend_from_slice(&transposed_bytes);
let misaligned = shifted.freeze().slice(1..transposed_bytes.len() + 1);
assert!(!misaligned.is_aligned(Alignment::of::<u64>()));
let untransposed = untranspose_bitbuffer(BitBuffer::new(misaligned, 2048));
assert_eq!(untransposed, expected);
assert_eq!(untransposed.slice(0..2048), bits);
}
#[test]
fn untranspose_chunk_aligned_bit_offset() {
let bits = make_validity_bits(3 * FL_CHUNK_SIZE);
let transposed = transpose_bitbuffer(bits.clone());
let view = transposed.slice(FL_CHUNK_SIZE..3 * FL_CHUNK_SIZE);
let untransposed = untranspose_bitbuffer(view);
assert_eq!(untransposed, bits.slice(FL_CHUNK_SIZE..3 * FL_CHUNK_SIZE));
}
}