#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitmapOperation {
None,
And,
Or,
Xor,
Not,
Diff,
}
trait BinaryOperator {
fn invoke_u64(a: u64, b: u64) -> u64;
fn invoke_u8(a: u8, b: u8) -> u8;
fn zeroes_when_exhausted() -> bool;
}
struct BitwiseAndOperator;
impl BinaryOperator for BitwiseAndOperator {
#[inline]
fn invoke_u64(a: u64, b: u64) -> u64 {
a & b
}
#[inline]
fn invoke_u8(a: u8, b: u8) -> u8 {
a & b
}
#[inline]
fn zeroes_when_exhausted() -> bool {
true
}
}
struct BitwiseOrOperator;
impl BinaryOperator for BitwiseOrOperator {
#[inline]
fn invoke_u64(a: u64, b: u64) -> u64 {
a | b
}
#[inline]
fn invoke_u8(a: u8, b: u8) -> u8 {
a | b
}
#[inline]
fn zeroes_when_exhausted() -> bool {
false
}
}
struct BitwiseXorOperator;
impl BinaryOperator for BitwiseXorOperator {
#[inline]
fn invoke_u64(a: u64, b: u64) -> u64 {
a ^ b
}
#[inline]
fn invoke_u8(a: u8, b: u8) -> u8 {
a ^ b
}
#[inline]
fn zeroes_when_exhausted() -> bool {
false
}
}
struct BitwiseAndNotOperator;
impl BinaryOperator for BitwiseAndNotOperator {
#[inline]
fn invoke_u64(a: u64, b: u64) -> u64 {
a & !b
}
#[inline]
fn invoke_u8(a: u8, b: u8) -> u8 {
a & !b
}
#[inline]
fn zeroes_when_exhausted() -> bool {
false
}
}
pub fn invoke_bit_operation_unsafe(
op: BitmapOperation,
srcs: &[&[u8]],
dst: &mut [u8],
shortest_src_length: usize,
) -> Result<(), &'static str> {
debug_assert!(matches!(
op,
BitmapOperation::Not
| BitmapOperation::And
| BitmapOperation::Or
| BitmapOperation::Xor
| BitmapOperation::Diff
));
debug_assert!(!srcs.is_empty());
debug_assert!(dst.len() >= shortest_src_length);
if srcs.len() == 1 {
if op == BitmapOperation::Diff {
return Err("BITOP DIFF operation requires at least two source bitmaps");
}
let src_bitmap = srcs[0];
if op == BitmapOperation::Not {
for (d, s) in dst.iter_mut().zip(src_bitmap) {
*d = !s;
}
} else {
dst[..src_bitmap.len()].copy_from_slice(src_bitmap);
}
} else if op == BitmapOperation::And {
invoke_nary_bitwise_operation::<BitwiseAndOperator>(srcs, dst, shortest_src_length);
} else if op == BitmapOperation::Or {
invoke_nary_bitwise_operation::<BitwiseOrOperator>(srcs, dst, shortest_src_length);
} else if op == BitmapOperation::Xor {
invoke_nary_bitwise_operation::<BitwiseXorOperator>(srcs, dst, shortest_src_length);
} else if op == BitmapOperation::Diff {
invoke_nary_bitwise_operation::<BitwiseAndNotOperator>(srcs, dst, shortest_src_length);
}
Ok(())
}
fn invoke_nary_bitwise_operation<O: BinaryOperator>(
srcs: &[&[u8]],
dst: &mut [u8],
shortest_src_length: usize,
) {
let mut cursors = vec![0usize; srcs.len()];
let mut dst_pos = 0usize;
let mut remaining = shortest_src_length;
let batch = remaining & !(64 * 8 - 1);
if batch > 0 {
vectorized512::<O>(srcs, &mut cursors, dst, dst_pos, batch);
dst_pos += batch;
remaining -= batch;
}
let batch = remaining & !(32 * 8 - 1);
if batch > 0 {
vectorized256::<O>(srcs, &mut cursors, dst, dst_pos, batch);
dst_pos += batch;
remaining -= batch;
}
let batch = remaining & !(16 * 8 - 1);
if batch > 0 {
vectorized128::<O>(srcs, &mut cursors, dst, dst_pos, batch);
dst_pos += batch;
remaining -= batch;
}
let words_end = dst_pos + remaining - (remaining & (size_of::<u64>() * 4 - 1));
while dst_pos < words_end {
for lane in 0..4 {
let off = dst_pos + lane * 8;
let mut v = u64::from_le_bytes(srcs[0][off..off + 8].try_into().unwrap());
for src in srcs.iter().skip(1) {
let b = u64::from_le_bytes(src[off..off + 8].try_into().unwrap());
v = O::invoke_u64(v, b);
}
dst[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
let chunk = size_of::<u64>() * 4;
for cur in cursors.iter_mut() {
*cur += chunk;
}
dst_pos += chunk;
}
while dst_pos < dst.len() {
let mut d00 = 0u8;
let first = srcs[0];
let cur0 = cursors[0];
if cur0 < first.len() {
d00 = first[cur0];
cursors[0] += 1;
}
for (i, cur) in cursors.iter_mut().enumerate().skip(1) {
let src = srcs[i];
if *cur < src.len() {
d00 = O::invoke_u8(d00, src[*cur]);
*cur += 1;
} else if O::zeroes_when_exhausted() {
d00 = 0;
}
}
dst[dst_pos] = d00;
dst_pos += 1;
}
}
fn vectorized512<O: BinaryOperator>(
srcs: &[&[u8]],
cursors: &mut [usize],
dst: &mut [u8],
dst_start: usize,
batch: usize,
) {
vectorized_n::<O>(srcs, cursors, dst, dst_start, batch, 64);
}
fn vectorized256<O: BinaryOperator>(
srcs: &[&[u8]],
cursors: &mut [usize],
dst: &mut [u8],
dst_start: usize,
batch: usize,
) {
vectorized_n::<O>(srcs, cursors, dst, dst_start, batch, 32);
}
fn vectorized128<O: BinaryOperator>(
srcs: &[&[u8]],
cursors: &mut [usize],
dst: &mut [u8],
dst_start: usize,
batch: usize,
) {
vectorized_n::<O>(srcs, cursors, dst, dst_start, batch, 16);
}
fn vectorized_n<O: BinaryOperator>(
srcs: &[&[u8]],
cursors: &mut [usize],
dst: &mut [u8],
dst_start: usize,
batch: usize,
width: usize,
) {
let mut dst_ptr = dst_start;
let dst_batch_end = dst_start + batch;
while dst_ptr < dst_batch_end {
for chunk in 0..width / 8 {
let off = dst_ptr + chunk * 8;
let mut v = u64::from_le_bytes(srcs[0][off..off + 8].try_into().unwrap());
for src in srcs.iter().skip(1) {
let b = u64::from_le_bytes(src[off..off + 8].try_into().unwrap());
v = O::invoke_u64(v, b);
}
dst[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
for cur in cursors.iter_mut() {
*cur += width;
}
dst_ptr += width;
}
}
#[cfg(test)]
mod tests {
use super::{BitmapOperation, invoke_bit_operation_unsafe};
fn naive(op: BitmapOperation, srcs: &[&[u8]], dst_len: usize) -> Vec<u8> {
let mut dst = vec![0u8; dst_len];
for i in 0..dst_len {
let mut b = if srcs[0].len() > i { srcs[0][i] } else { 0 };
for s in &srcs[1..] {
b = if s.len() > i {
match op {
BitmapOperation::And => b & s[i],
BitmapOperation::Or => b | s[i],
BitmapOperation::Xor => b ^ s[i],
BitmapOperation::Diff => b & !s[i],
_ => b,
}
} else if op == BitmapOperation::And {
0
} else {
b
};
}
dst[i] = b;
}
dst
}
fn run(op: BitmapOperation, srcs: &[&[u8]]) -> Vec<u8> {
let shortest = srcs.iter().map(|s| s.len()).min().unwrap();
let longest = srcs.iter().map(|s| s.len()).max().unwrap();
let mut dst = vec![0u8; longest];
invoke_bit_operation_unsafe(op, srcs, &mut dst, shortest).unwrap();
dst
}
#[test]
fn nary_matches_naive() {
let a: &[u8] = &[0b1100_0011, 0xff, 0x00, 0x0f, 0xf0, 0x55, 0xaa];
let b: &[u8] = &[0b1010_1010, 0x01, 0x81];
let c: &[u8] = &[0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff];
for (op, srcs) in [
(BitmapOperation::And, vec![a, b]),
(BitmapOperation::Or, vec![a, b]),
(BitmapOperation::Xor, vec![a, b]),
(BitmapOperation::Diff, vec![a, b]),
(BitmapOperation::And, vec![c, a, b]),
(BitmapOperation::Or, vec![c, a, b]),
(BitmapOperation::Xor, vec![c, a, b]),
(BitmapOperation::Diff, vec![c, a, b]),
(BitmapOperation::And, vec![a, b, c, a]),
] {
assert_eq!(
run(op, &srcs),
naive(op, &srcs, srcs.iter().map(|s| s.len()).max().unwrap()),
"{op:?}"
);
}
}
#[test]
fn not_and_single_source() {
let a: &[u8] = &[0x0f, 0xf0, 0xff];
let mut dst = vec![0u8; 3];
invoke_bit_operation_unsafe(BitmapOperation::Not, &[a], &mut dst, 3).unwrap();
assert_eq!(dst, vec![0xf0, 0x0f, 0x00]);
let mut dst = vec![0u8; 5];
invoke_bit_operation_unsafe(BitmapOperation::And, &[a], &mut dst, 3).unwrap();
assert_eq!(dst[..3], a[..3]);
let mut dst = vec![0u8; 3];
assert!(invoke_bit_operation_unsafe(BitmapOperation::Diff, &[a], &mut dst, 3).is_err());
}
}