use spiral_rs::aligned_memory::AlignedMemory64;
pub fn as_bytes(a_m: &AlignedMemory64) -> &[u8] {
unsafe { std::slice::from_raw_parts(a_m.as_ptr() as *const u8, a_m.len() * 8) }
}
pub fn as_bytes_mut(a_m: &mut AlignedMemory64) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(a_m.as_ptr() as *mut u8, a_m.len() * 8) }
}
pub fn write_bits(data: &mut [u8], mut val: u64, bit_offs: usize, mut num_bits: usize) {
let mut byte_index = bit_offs / 8;
let mut bit_index = bit_offs % 8;
while num_bits > 0 && byte_index < data.len() {
let bits_to_write = std::cmp::min(8 - bit_index, num_bits);
let bitmask = (1 << bits_to_write) - 1;
let bits = (val & bitmask) << bit_index;
data[byte_index] |= bits as u8;
num_bits -= bits_to_write;
bit_index += bits_to_write;
if bit_index == 8 {
byte_index += 1;
bit_index = 0;
}
val >>= bits_to_write;
}
}
pub fn read_bits(data: &[u8], bit_offs: usize, num_bits: usize) -> u64 {
if num_bits > 64 || num_bits == 0 {
panic!("Invalid number of bits: {}", num_bits);
}
let byte_pos = bit_offs / 8;
let mut bit_pos = bit_offs % 8;
let mut result: u64 = 0;
let mut remaining_bits = num_bits;
for i in byte_pos..data.len() {
let can_take = std::cmp::min(8 - bit_pos, remaining_bits);
let value = if can_take < 8 {
(data[i] >> bit_pos) & ((1 << can_take) - 1)
} else {
data[i] >> bit_pos
};
result |= (value as u64) << (num_bits - remaining_bits);
remaining_bits -= can_take;
if remaining_bits == 0 {
break;
}
if bit_pos + can_take < 8 {
let from_next_byte = data[i] & ((1 << (bit_pos + can_take)) - 1);
result |= (from_next_byte as u64) << (num_bits - remaining_bits);
remaining_bits -= bit_pos + can_take;
}
bit_pos = 0; }
result
}
pub fn u64s_to_contiguous_bytes(data: &[u64], inp_mod_bits: usize) -> Vec<u8> {
let total_sz = ((data.len() as f64 * inp_mod_bits as f64) / 8.0).ceil() as usize;
let mut out = vec![0u8; total_sz];
let mut bit_offs = 0;
for i in 0..data.len() {
write_bits(&mut out, data[i], bit_offs, inp_mod_bits);
bit_offs += inp_mod_bits;
}
out
}
pub fn contiguous_bytes_to_u64s(data: &[u8], out_mod_bits: usize) -> Vec<u64> {
let mut out = vec![0u64; (data.len() * 8) / out_mod_bits];
let mut bit_offs = 0;
for i in 0..out.len() {
out[i] = read_bits(&data, bit_offs, out_mod_bits);
bit_offs += out_mod_bits;
}
out
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_write_and_read_bits() {
let mut buffer = [0u8; 4];
write_bits(&mut buffer, 0b11010101, 1, 6);
assert_eq!(buffer, [0b00101010, 0b00000000, 0b00000000, 0b00000000]);
let value = read_bits(&buffer, 1, 6);
assert_eq!(value, 0b010101);
let mut buffer2 = [0u8; 4];
write_bits(&mut buffer2, 0b11111111, 0, 8);
assert_eq!(buffer2, [0b11111111, 0b00000000, 0b00000000, 0b00000000]);
let value2 = read_bits(&buffer2, 0, 8);
assert_eq!(value2, 0b11111111);
let mut buffer3 = [0u8; 4];
write_bits(&mut buffer3, 0b10101010, 4, 4);
assert_eq!(buffer3, [0b10100000, 0b00000000, 0b00000000, 0b00000000]);
let value3 = read_bits(&buffer3, 4, 4);
assert_eq!(value3, 0b1010);
let num = 42;
let bits_per = 14;
let total_sy_bytes = ((num * bits_per) as f64 / 8.0).ceil() as usize;
let vals = (0..num)
.map(|_| fastrand::u64(..) % (1 << bits_per))
.collect::<Vec<_>>();
let mut buffer4 = vec![0u8; total_sy_bytes];
let mut bit_offs = 0;
for i in 0..num {
write_bits(&mut buffer4, vals[i], bit_offs, bits_per);
bit_offs += bits_per;
}
for i in 0..num {
let val = read_bits(&buffer4, i * bits_per, bits_per);
assert_eq!(val, vals[i]);
}
}
#[test]
fn u64s_contiguous_bytes_14bit_roundtrip() {
let bits = 14;
let n = 2048;
let vals: Vec<u64> = (0..n).map(|i| (i as u64 * 997) % (1 << bits)).collect();
let bytes = u64s_to_contiguous_bytes(&vals, bits);
let recovered = contiguous_bytes_to_u64s(&bytes, bits);
assert_eq!(recovered.len(), vals.len());
assert_eq!(recovered, vals);
}
#[test]
fn u64s_contiguous_bytes_14bit_edge_cases() {
let bits = 14;
let max = (1u64 << bits) - 1;
let vals = vec![0, 1, max, max - 1, 1 << 13, (1 << 13) - 1, 0b10101010101010 & max];
let bytes = u64s_to_contiguous_bytes(&vals, bits);
let recovered = contiguous_bytes_to_u64s(&bytes, bits);
assert_eq!(recovered, vals);
}
#[test]
fn u64s_contiguous_bytes_various_bit_widths() {
for bits in [1, 2, 7, 8, 13, 14, 15, 16, 28, 32, 64] {
let max = if bits == 64 { u64::MAX } else { (1u64 << bits) - 1 };
let n = 100;
let vals: Vec<u64> = (0..n).map(|i| (i as u64 * 12347) & max).collect();
let bytes = u64s_to_contiguous_bytes(&vals, bits);
let recovered = contiguous_bytes_to_u64s(&bytes, bits);
for i in 0..n {
assert_eq!(recovered[i], vals[i], "round-trip failed at index {i} for bits={bits}");
}
}
}
#[test]
fn read_bits_cross_byte_boundary() {
let mut buf = [0u8; 4];
write_bits(&mut buf, 0b11_1111_0000_0011, 3, 14);
let val = read_bits(&buf, 3, 14);
assert_eq!(val, 0b11_1111_0000_0011);
}
#[test]
fn u64s_contiguous_bytes_single_element() {
for bits in [1, 7, 14, 28, 64] {
let max = if bits == 64 { u64::MAX } else { (1u64 << bits) - 1 };
let vals = vec![max];
let bytes = u64s_to_contiguous_bytes(&vals, bits);
let recovered = contiguous_bytes_to_u64s(&bytes, bits);
assert!(recovered.len() >= 1, "must recover at least 1 element for bits={bits}");
assert_eq!(recovered[0], vals[0], "single-element round-trip failed for bits={bits}");
}
}
#[test]
#[should_panic(expected = "Invalid number of bits")]
fn read_bits_zero_width_panics() {
let buf = [0u8; 4];
let _ = read_bits(&buf, 0, 0);
}
}