1use std::hint::cold_path;
5use std::sync::Arc;
6
7use vortex_buffer::BitBuffer;
8use vortex_buffer::BufferMut;
9use vortex_buffer::CpuKernel;
10use vortex_error::VortexExpect;
11
12use crate::Mask;
13use crate::MaskValues;
14use crate::MaskValuesRef;
15
16#[cfg(target_arch = "x86_64")]
17mod bmi2;
18#[cfg(target_arch = "aarch64")]
19mod sve;
20
21trait DepositBits {
22 fn deposit_bits(source: u64, mask: u64, mask_count: usize) -> u64;
28}
29
30trait SelectBit {
31 fn select_bit_position(word: u64, rank: usize) -> usize;
34}
35
36struct Portable;
39
40impl DepositBits for Portable {
41 #[allow(clippy::inline_always)]
43 #[inline(always)]
44 fn deposit_bits(source: u64, mask: u64, mask_count: usize) -> u64 {
45 debug_assert_eq!(mask_count, mask.count_ones() as usize);
46
47 match mask_count {
48 0..=2 => deposit_two_lowest(source, mask),
49 62.. => deposit_two_holes(source, mask),
50 _ => deposit_bytewise(source, mask),
51 }
52 }
53}
54
55impl SelectBit for Portable {
56 #[inline]
57 fn select_bit_position(word: u64, rank: usize) -> usize {
58 select_bit_position_portable(word, rank)
59 }
60}
61
62struct PortableLoop;
65
66impl DepositBits for PortableLoop {
67 #[inline]
68 fn deposit_bits(mut source: u64, mut mask: u64, mask_count: usize) -> u64 {
69 debug_assert_eq!(mask_count, mask.count_ones() as usize);
70
71 if mask_count <= 2 {
72 return deposit_two_lowest(source, mask);
73 }
74
75 let mut result = 0u64;
76 while mask != 0 && source != 0 {
77 let bit = mask & mask.wrapping_neg();
78 result |= bit & (source & 1).wrapping_neg();
79 source >>= 1;
80 mask &= mask - 1;
81 }
82 result
83 }
84}
85
86const fn bytes(b: u8) -> u64 {
88 b as u64 * 0x0101_0101_0101_0101
89}
90
91#[allow(clippy::inline_always)]
93#[inline(always)]
94fn deposit_two_lowest(source: u64, mask: u64) -> u64 {
95 let lowest = mask & mask.wrapping_neg();
96 let rest = mask ^ lowest;
97 let second = rest & rest.wrapping_neg();
98 (lowest & (source & 1).wrapping_neg()) | (second & ((source >> 1) & 1).wrapping_neg())
99}
100
101#[allow(clippy::inline_always)]
103#[inline(always)]
104fn deposit_two_holes(mut source: u64, mask: u64) -> u64 {
105 let mut holes = !mask;
106 for _ in 0..2 {
107 let hole = holes & holes.wrapping_neg();
108 let below = hole.wrapping_sub(1);
109 source = (source & below) | ((source << 1) & !below);
110 holes ^= hole;
111 }
112 source & mask
113}
114
115#[allow(clippy::inline_always)]
122#[inline(always)]
123fn deposit_bytewise(source: u64, mask: u64) -> u64 {
124 let pairs = mask - ((mask >> 1) & bytes(0x55));
125 let nibbles = (pairs & bytes(0x33)) + ((pairs >> 2) & bytes(0x33));
126 let counts = (nibbles + (nibbles >> 4)) & bytes(0x0F);
127 let below = counts.wrapping_mul(bytes(1)) << 8;
129
130 let mut packed = mask;
134 let mut zeros = !mask;
135 let mut landed = [0u64; 3];
136 for (stage, landed) in landed.iter_mut().enumerate() {
137 let shift = 1 << stage;
138 let mut parity = zeros;
139 let mut len = shift;
140 while len < 8 {
141 parity ^= (parity << len) & bytes(0xFF << len);
142 len <<= 1;
143 }
144 let moving = packed & parity;
145 *landed = (moving >> shift) & bytes(0xFF >> shift);
146 packed ^= moving ^ *landed;
147 zeros &= !parity;
148 zeros ^= (zeros >> shift) & bytes(0xFF >> shift);
149 }
150
151 let mut result = 0;
153 for byte in 0..8 {
154 let from = (below >> (8 * byte)) & 0xFF;
155 result |= ((source >> from) & 0xFF) << (8 * byte);
156 }
157 result &= packed;
158 for (stage, landed) in landed.iter().enumerate().rev() {
159 result = (result & !landed) | ((result & landed) << (1 << stage));
160 }
161 result & mask
162}
163
164#[inline]
165fn select_bit_position_portable(word: u64, mut rank: usize) -> usize {
166 debug_assert!(rank < word.count_ones() as usize);
167 let mut bit_offset = 0usize;
168 for byte in word.to_le_bytes() {
169 let count = byte.count_ones() as usize;
170 if rank < count {
171 let mut bits = byte;
172 for _ in 0..rank {
173 bits &= bits - 1;
174 }
175
176 return bit_offset + bits.trailing_zeros() as usize;
177 }
178
179 rank -= count;
180 bit_offset += 8;
181 }
182
183 debug_assert!(false, "rank out of bounds");
184 0
185}
186
187struct RankBits<'a> {
193 bytes: &'a [u8],
194 start: usize,
195 tail: [u8; 32],
198 tail_start: usize,
199}
200
201impl<'a> RankBits<'a> {
202 fn new(buffer: &'a BitBuffer) -> Self {
203 let bytes = buffer.inner().as_slice();
204 let tail_start = bytes.len().saturating_sub(16);
205 let mut tail = [0; 32];
206 tail[..bytes.len() - tail_start].copy_from_slice(&bytes[tail_start..]);
207 Self {
208 bytes,
209 start: buffer.offset(),
210 tail,
211 tail_start,
212 }
213 }
214
215 #[inline]
217 #[expect(clippy::cast_possible_truncation)]
218 fn read(&self, offset: usize) -> u64 {
219 let position = self.start + offset;
220 let (byte, shift) = (position / 8, position % 8);
221 let window = match self.bytes.get(byte..).and_then(<[u8]>::first_chunk) {
223 Some(window) => window,
224 None => {
225 cold_path();
227 self.tail
228 .get(byte - self.tail_start..)
229 .and_then(<[u8]>::first_chunk)
230 .unwrap_or(&[0; 16])
231 }
232 };
233 (u128::from_le_bytes(*window) >> shift) as u64
234 }
235}
236
237#[inline]
238fn mask_from_buffer(buffer: BitBuffer, true_count: usize) -> Mask {
239 let len = buffer.len();
240 if true_count == 0 {
241 return Mask::new_false(len);
242 }
243 if true_count == len {
244 return Mask::new_true(len);
245 }
246
247 Mask::Values(Arc::new(MaskValues {
248 buffer,
249 indices: Default::default(),
250 slices: Default::default(),
251 true_count,
252 density: true_count as f64 / len as f64,
253 }))
254}
255
256#[inline]
257fn push_result_chunk<D: DepositBits>(
258 result: &mut BufferMut<u64>,
259 self_chunk: u64,
260 self_count: usize,
261 rank_bits: u64,
262) {
263 let chunk = D::deposit_bits(rank_bits, self_chunk, self_count);
264 unsafe { result.push_unchecked(chunk) };
266}
267
268#[allow(clippy::inline_always)]
271#[inline(always)]
272fn intersect_bit_buffers<D: DepositBits>(
273 self_buffer: &BitBuffer,
274 mask_buffer: &BitBuffer,
275 true_count: usize,
276) -> Mask {
277 let len = self_buffer.len();
278 let mut result = BufferMut::with_capacity(len.div_ceil(64));
279 let rank_bits = RankBits::new(mask_buffer);
280 let self_chunks = self_buffer.chunks();
281 let mut rank_offset = 0;
282
283 for self_chunk in self_chunks.iter() {
284 let self_count = self_chunk.count_ones() as usize;
285 let source = rank_bits.read(rank_offset);
286 push_result_chunk::<D>(&mut result, self_chunk, self_count, source);
287 rank_offset += self_count;
288 }
289
290 if self_chunks.remainder_len() != 0 {
291 let self_chunk = self_chunks.remainder_bits();
292 let self_count = self_chunk.count_ones() as usize;
293 let source = rank_bits.read(rank_offset);
294 push_result_chunk::<D>(&mut result, self_chunk, self_count, source);
295 }
296
297 mask_from_buffer(
298 BitBuffer::new(result.freeze().into_byte_buffer(), len),
299 true_count,
300 )
301}
302
303#[allow(clippy::inline_always)]
304#[inline(always)]
305fn intersect_bit_buffer_by_rank_indices<D: DepositBits>(
306 self_buffer: &BitBuffer,
307 mask_indices: &[usize],
308) -> Mask {
309 let len = self_buffer.len();
310 let mut result = BufferMut::with_capacity(len.div_ceil(64));
311 let self_chunks = self_buffer.chunks();
312 let mut rank_base = 0usize;
313 let mut rank_idx = 0usize;
314
315 for self_chunk in self_chunks.iter() {
316 let self_count = self_chunk.count_ones() as usize;
317 let next_rank_base = rank_base + self_count;
318 let rank_bits = rank_bits_for_chunk(mask_indices, &mut rank_idx, rank_base, next_rank_base);
319 push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
320 rank_base = next_rank_base;
321 }
322
323 if self_chunks.remainder_len() != 0 {
324 let self_chunk = self_chunks.remainder_bits();
325 let self_count = self_chunk.count_ones() as usize;
326 let next_rank_base = rank_base + self_count;
327 let rank_bits = rank_bits_for_chunk(mask_indices, &mut rank_idx, rank_base, next_rank_base);
328 push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
329 }
330
331 debug_assert_eq!(rank_idx, mask_indices.len());
332
333 mask_from_buffer(
334 BitBuffer::new(result.freeze().into_byte_buffer(), len),
335 mask_indices.len(),
336 )
337}
338
339#[allow(clippy::inline_always)]
346#[inline(always)]
347fn intersect_mask_driven<S, I>(self_buffer: &BitBuffer, mask_indices: I, true_count: usize) -> Mask
348where
349 S: SelectBit,
350 I: Iterator<Item = usize>,
351{
352 let len = self_buffer.len();
353 if true_count == 0 {
354 return Mask::new_false(len);
355 }
356
357 let mut chunk_iter = self_buffer.chunks().iter_padded();
358
359 let mut current_chunk = chunk_iter.next().unwrap_or(0);
360 let mut current_count = current_chunk.count_ones() as usize;
361 let mut current_chunk_idx = 0usize;
362 let mut rank_before = 0usize;
363
364 let mut output = Vec::with_capacity(true_count);
365
366 for global_rank in mask_indices {
367 while rank_before + current_count <= global_rank {
368 rank_before += current_count;
369 current_chunk_idx += 1;
370 current_chunk = chunk_iter.next().vortex_expect("mask index out of bounds");
371 current_count = current_chunk.count_ones() as usize;
372 }
373
374 let local_rank = global_rank - rank_before;
375 let bit_pos = S::select_bit_position(current_chunk, local_rank);
376 output.push(current_chunk_idx * 64 + bit_pos);
377 }
378
379 debug_assert_eq!(output.len(), true_count);
380 Mask::from_indices(len, output)
381}
382
383#[inline]
384fn rank_bits_for_chunk(
385 mask_indices: &[usize],
386 rank_idx: &mut usize,
387 rank_base: usize,
388 next_rank_base: usize,
389) -> u64 {
390 let mut rank_bits = 0u64;
391 while let Some(&rank) = mask_indices.get(*rank_idx) {
392 if rank >= next_rank_base {
393 break;
394 }
395 rank_bits |= 1u64 << (rank - rank_base);
396 *rank_idx += 1;
397 }
398 rank_bits
399}
400
401fn intersect_by_rank_indices(len: usize, self_indices: &[usize], mask_indices: &[usize]) -> Mask {
402 Mask::from_indices(
403 len,
404 mask_indices.iter().map(|idx| {
405 unsafe { *self_indices.get_unchecked(*idx) }
408 }),
409 )
410}
411
412const PORTABLE_LOOP_MAX_DENSITY: f64 = 1.0 / 16.0;
419
420type IntersectBuffers = unsafe fn(&BitBuffer, &BitBuffer, usize) -> Mask;
421type IntersectRankIndices = unsafe fn(&BitBuffer, &[usize]) -> Mask;
422
423fn select_bit_buffers<P: DepositBits>() -> IntersectBuffers {
425 #[cfg(target_arch = "x86_64")]
426 {
427 if std::arch::is_x86_feature_detected!("bmi2")
428 && std::arch::is_x86_feature_detected!("popcnt")
429 {
430 return bmi2::intersect_bit_buffers_bmi2;
431 }
432 }
433 #[cfg(target_arch = "aarch64")]
434 {
435 if std::arch::is_aarch64_feature_detected!("sve2-bitperm") {
436 return sve::intersect_bit_buffers_sve2;
437 }
438 }
439 intersect_bit_buffers::<P>
440}
441
442fn select_rank_indices<P: DepositBits>() -> IntersectRankIndices {
444 #[cfg(target_arch = "x86_64")]
445 {
446 if std::arch::is_x86_feature_detected!("bmi2")
447 && std::arch::is_x86_feature_detected!("popcnt")
448 {
449 return bmi2::intersect_bit_buffer_by_rank_indices_bmi2;
450 }
451 }
452 #[cfg(target_arch = "aarch64")]
453 {
454 if std::arch::is_aarch64_feature_detected!("sve2-bitperm") {
455 return sve::intersect_bit_buffer_by_rank_indices_sve2;
456 }
457 }
458 intersect_bit_buffer_by_rank_indices::<P>
459}
460
461#[inline]
462fn intersect_bit_buffers_dispatch(
463 self_buffer: &BitBuffer,
464 mask_buffer: &BitBuffer,
465 true_count: usize,
466 self_density: f64,
467) -> Mask {
468 static KERNEL: CpuKernel<IntersectBuffers> = CpuKernel::new(select_bit_buffers::<Portable>);
469 static SPARSE_KERNEL: CpuKernel<IntersectBuffers> =
470 CpuKernel::new(select_bit_buffers::<PortableLoop>);
471 let kernel = if self_density < PORTABLE_LOOP_MAX_DENSITY {
472 &SPARSE_KERNEL
473 } else {
474 &KERNEL
475 };
476 unsafe { kernel.get()(self_buffer, mask_buffer, true_count) }
479}
480
481#[inline]
482fn intersect_rank_indices_dispatch(
483 self_buffer: &BitBuffer,
484 mask_indices: &[usize],
485 self_density: f64,
486) -> Mask {
487 static KERNEL: CpuKernel<IntersectRankIndices> =
488 CpuKernel::new(select_rank_indices::<Portable>);
489 static SPARSE_KERNEL: CpuKernel<IntersectRankIndices> =
490 CpuKernel::new(select_rank_indices::<PortableLoop>);
491 let kernel = if self_density < PORTABLE_LOOP_MAX_DENSITY {
492 &SPARSE_KERNEL
493 } else {
494 &KERNEL
495 };
496 unsafe { kernel.get()(self_buffer, mask_indices) }
499}
500
501#[inline]
502fn intersect_mask_driven_dispatch<I>(
503 self_buffer: &BitBuffer,
504 mask_indices: I,
505 true_count: usize,
506) -> Mask
507where
508 I: Iterator<Item = usize>,
509{
510 #[cfg(target_arch = "x86_64")]
511 if std::arch::is_x86_feature_detected!("bmi2") && std::arch::is_x86_feature_detected!("popcnt")
512 {
513 return unsafe { bmi2::intersect_mask_driven_bmi2(self_buffer, mask_indices, true_count) };
515 }
516
517 #[cfg(target_arch = "aarch64")]
518 if std::arch::is_aarch64_feature_detected!("sve2-bitperm") {
519 return unsafe { sve::intersect_mask_driven_sve2(self_buffer, mask_indices, true_count) };
521 }
522
523 intersect_mask_driven::<Portable, _>(self_buffer, mask_indices, true_count)
524}
525
526fn mask_is_sparse(values: &MaskValuesRef) -> bool {
530 values.true_count().saturating_mul(64) < values.len()
531}
532
533fn rank_mask_is_sparse(values: &MaskValuesRef) -> bool {
538 values.true_count().saturating_mul(32) < values.len()
539}
540
541impl Mask {
542 pub fn intersect_by_rank(&self, mask: &Mask) -> Mask {
561 assert_eq!(self.true_count(), mask.len());
562
563 match (self, mask) {
564 (Self::AllTrue(_), _) => mask.clone(),
565 (_, Self::AllTrue(_)) => self.clone(),
566 (Self::AllFalse(_), _) | (_, Self::AllFalse(_)) => Self::new_false(self.len()),
567 (Self::Values(self_values), Self::Values(mask_values)) => {
568 if let Some(mask_indices) = mask_values.indices.get() {
575 if let Some(self_indices) = self_values.indices.get()
576 && mask_indices.len() < self.len().div_ceil(64)
577 {
578 return intersect_by_rank_indices(self.len(), self_indices, mask_indices);
579 }
580
581 let self_is_very_sparse = mask_is_sparse(self_values);
582 let mask_is_very_sparse = rank_mask_is_sparse(mask_values);
583
584 if self_is_very_sparse {
585 return intersect_by_rank_indices(
586 self.len(),
587 self_values.indices(),
588 mask_indices,
589 );
590 }
591
592 if mask_is_very_sparse {
593 return intersect_mask_driven_dispatch(
594 self_values.bit_buffer(),
595 mask_indices.iter().copied(),
596 mask_values.true_count(),
597 );
598 }
599
600 if mask_indices.len().saturating_mul(4) > mask.len() {
601 return intersect_bit_buffers_dispatch(
602 self_values.bit_buffer(),
603 mask_values.bit_buffer(),
604 mask_values.true_count(),
605 self_values.density(),
606 );
607 }
608
609 return intersect_rank_indices_dispatch(
610 self_values.bit_buffer(),
611 mask_indices,
612 self_values.density(),
613 );
614 }
615
616 let self_is_very_sparse = mask_is_sparse(self_values);
617 let mask_is_very_sparse = rank_mask_is_sparse(mask_values);
618
619 if self_is_very_sparse {
620 return intersect_by_rank_indices(
621 self.len(),
622 self_values.indices(),
623 mask_values.indices(),
624 );
625 }
626
627 if mask_is_very_sparse {
628 return intersect_mask_driven_dispatch(
629 self_values.bit_buffer(),
630 mask_values.bit_buffer().set_indices(),
631 mask_values.true_count(),
632 );
633 }
634
635 intersect_bit_buffers_dispatch(
636 self_values.bit_buffer(),
637 mask_values.bit_buffer(),
638 mask_values.true_count(),
639 self_values.density(),
640 )
641 }
642 }
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use rstest::rstest;
649 use vortex_buffer::BitBuffer;
650
651 use super::DepositBits;
652 use super::Portable;
653 use super::PortableLoop;
654 #[cfg(target_arch = "aarch64")]
655 use super::SelectBit;
656 use super::intersect_bit_buffer_by_rank_indices;
657 use super::intersect_bit_buffers;
658 #[cfg(target_arch = "aarch64")]
659 use super::sve::Sve2Bdep;
660 use crate::Mask;
661
662 #[test]
663 fn mask_bitand_all_as_bit_and() {
664 let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, true, true, true, true]));
665 let mask = Mask::from_buffer(BitBuffer::from_iter(vec![false, true, false, true, true]));
666 assert_eq!(
667 this.intersect_by_rank(&mask),
668 Mask::from_indices(5, vec![1, 3, 4])
669 );
670 }
671
672 #[test]
673 fn mask_bitand_all_true() {
674 let this = Mask::from_buffer(BitBuffer::from_iter(vec![false, false, true, true, true]));
675 let mask = Mask::from_buffer(BitBuffer::from_iter(vec![true, true, true]));
676 assert_eq!(
677 this.intersect_by_rank(&mask),
678 Mask::from_indices(5, vec![2, 3, 4])
679 );
680 }
681
682 #[test]
683 fn mask_bitand_true() {
684 let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, false, true, true]));
685 let mask = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, true]));
686 assert_eq!(
687 this.intersect_by_rank(&mask),
688 Mask::from_indices(5, vec![0, 4])
689 );
690 }
691
692 #[test]
693 fn mask_bitand_false() {
694 let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, false, true, true]));
695 let mask = Mask::from_buffer(BitBuffer::from_iter(vec![false, false, false]));
696 assert_eq!(this.intersect_by_rank(&mask), Mask::from_indices(5, vec![]));
697 }
698
699 #[test]
700 fn mask_intersect_by_rank_all_false() {
701 let this = Mask::AllFalse(10);
702 let mask = Mask::AllFalse(0);
703 assert_eq!(this.intersect_by_rank(&mask), Mask::AllFalse(10));
704 }
705
706 #[rstest]
707 #[case::all_true_with_all_true(
708 Mask::new_true(5),
709 Mask::new_true(5),
710 vec![0, 1, 2, 3, 4]
711 )]
712 #[case::all_true_with_all_false(
713 Mask::new_true(5),
714 Mask::new_false(5),
715 vec![]
716 )]
717 #[case::all_false_with_any(
718 Mask::new_false(10),
719 Mask::new_true(0),
720 vec![]
721 )]
722 #[case::indices_with_all_true(
723 Mask::from_indices(10, vec![2, 5, 7, 9]),
724 Mask::new_true(4),
725 vec![2, 5, 7, 9]
726 )]
727 #[case::indices_with_all_false(
728 Mask::from_indices(10, vec![2, 5, 7, 9]),
729 Mask::new_false(4),
730 vec![]
731 )]
732 fn test_intersect_by_rank_special_cases(
733 #[case] base_mask: Mask,
734 #[case] rank_mask: Mask,
735 #[case] expected_indices: Vec<usize>,
736 ) {
737 let result = base_mask.intersect_by_rank(&rank_mask);
738 assert_eq!(result.len(), base_mask.len());
739
740 match result.indices() {
741 crate::AllOr::All => assert_eq!(expected_indices.len(), result.len()),
742 crate::AllOr::None => assert!(expected_indices.is_empty()),
743 crate::AllOr::Some(indices) => assert_eq!(indices, &expected_indices[..]),
744 }
745 }
746
747 #[test]
748 fn test_intersect_by_rank_example() {
749 let m1 = Mask::from_iter([true, false, false, true, true, true, false, true]);
751 let m2 = Mask::from_iter([false, false, true, false, true]);
752 let result = m1.intersect_by_rank(&m2);
753 let expected = Mask::from_iter([false, false, false, false, true, false, false, true]);
754 assert_eq!(result, expected);
755 }
756
757 #[test]
758 #[should_panic]
759 fn test_intersect_by_rank_wrong_length() {
760 let m1 = Mask::from_indices(10, vec![2, 5, 7]); let m2 = Mask::new_true(5); m1.intersect_by_rank(&m2);
763 }
764
765 #[rstest]
766 #[case::single_element(
767 vec![3],
768 vec![true],
769 vec![3]
770 )]
771 #[case::single_element_masked(
772 vec![3],
773 vec![false],
774 vec![]
775 )]
776 #[case::alternating(
777 vec![0, 2, 4, 6, 8],
778 vec![true, false, true, false, true],
779 vec![0, 4, 8]
780 )]
781 #[case::consecutive(
782 vec![5, 6, 7, 8, 9],
783 vec![false, true, true, true, false],
784 vec![6, 7, 8]
785 )]
786 fn test_intersect_by_rank_patterns(
787 #[case] base_indices: Vec<usize>,
788 #[case] rank_pattern: Vec<bool>,
789 #[case] expected_indices: Vec<usize>,
790 ) {
791 let base = Mask::from_indices(10, base_indices);
792 let rank = Mask::from_iter(rank_pattern);
793 let result = base.intersect_by_rank(&rank);
794 assert_eq!(result.len(), base.len());
795
796 match result.indices() {
797 crate::AllOr::Some(indices) => assert_eq!(indices, &expected_indices[..]),
798 crate::AllOr::None => assert!(expected_indices.is_empty()),
799 _ => panic!("Unexpected result"),
800 }
801 }
802
803 #[rstest]
804 #[case::dense_len_1024(1024, 31, 0.5, 0.5)]
806 #[case::sparse_mask_1pct(1024, 17, 0.5, 0.01)]
809 #[case::sparse_mask_2pct(2048, 0, 0.5, 0.02)]
810 #[case::very_sparse_mask_with_offsets(513, 5, 0.5, 0.005)]
811 fn test_intersect_by_rank_density_matrix(
812 #[case] base_len: usize,
813 #[case] base_offset: usize,
814 #[case] base_density: f64,
815 #[case] rank_density: f64,
816 ) {
817 #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
818 let base_threshold = (base_density * 1024.0) as usize;
819 #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
820 let rank_threshold = (rank_density * 1024.0) as usize;
821
822 let base_source: Vec<bool> = (0..base_len + base_offset + 16)
823 .map(|i| (i * 7 + 13) % 1024 < base_threshold)
824 .collect();
825 let base_bits = base_source[base_offset..base_offset + base_len].to_vec();
826 let base = Mask::from_buffer(
827 BitBuffer::from(base_source).slice(base_offset..base_offset + base_len),
828 );
829
830 let rank_len = base.true_count();
831 let rank_bits: Vec<bool> = (0..rank_len)
832 .map(|i| (i * 11 + 7) % 1024 < rank_threshold)
833 .collect();
834 let rank_from_buffer = Mask::from_buffer(BitBuffer::from(rank_bits.clone()));
835 let rank_indices_vec = rank_bits
836 .iter()
837 .enumerate()
838 .filter_map(|(idx, &v)| v.then_some(idx))
839 .collect::<Vec<_>>();
840 let rank_from_indices = Mask::from_indices(rank_len, rank_indices_vec);
841
842 let expected = expected_intersect_by_rank(&base_bits, &rank_bits);
843
844 assert_eq!(
845 base.intersect_by_rank(&rank_from_buffer),
846 expected,
847 "uncached rank"
848 );
849 assert_eq!(
850 base.intersect_by_rank(&rank_from_indices),
851 expected,
852 "cached rank"
853 );
854 }
855
856 #[rstest]
857 #[case::short(37, 0, 0)]
858 #[case::base_offset(257, 5, 0)]
859 #[case::rank_offset(257, 0, 3)]
860 #[case::both_offsets(513, 6, 5)]
861 fn test_intersect_by_rank_bitbuffer_paths_with_offsets(
862 #[case] base_len: usize,
863 #[case] base_offset: usize,
864 #[case] rank_offset: usize,
865 ) {
866 let base_source: Vec<bool> = (0..base_len + base_offset + 16)
867 .map(|i| (i % 3 == 0) ^ (i % 11 == 0) ^ (i % 17 == 0))
868 .collect();
869 let base_bits = base_source[base_offset..base_offset + base_len].to_vec();
870 let base = Mask::from_buffer(
871 BitBuffer::from(base_source).slice(base_offset..base_offset + base_len),
872 );
873
874 let rank_len = base.true_count();
875 let rank_bits: Vec<bool> = (0..rank_len)
876 .map(|i| (i % 5 == 0) || (i % 13 == 3))
877 .collect();
878 let mut rank_source = vec![false; rank_offset];
879 rank_source.extend(rank_bits.iter().copied());
880 rank_source.extend([true, false, true, false, true, false, true, false]);
881
882 let rank_from_buffer = Mask::from_buffer(
883 BitBuffer::from(rank_source).slice(rank_offset..rank_offset + rank_len),
884 );
885 let rank_indices = rank_bits
886 .iter()
887 .enumerate()
888 .filter_map(|(idx, &value)| value.then_some(idx))
889 .collect::<Vec<_>>();
890 let rank_from_indices = Mask::from_indices(rank_len, rank_indices);
891
892 let expected = expected_intersect_by_rank(&base_bits, &rank_bits);
893
894 assert_eq!(base.intersect_by_rank(&rank_from_buffer), expected);
895 assert_eq!(base.intersect_by_rank(&rank_from_indices), expected);
896 }
897
898 fn xorshift(state: &mut u64) -> u64 {
900 *state ^= *state << 13;
901 *state ^= *state >> 7;
902 *state ^= *state << 17;
903 *state
904 }
905
906 fn deposit_reference(mut source: u64, mut mask: u64) -> u64 {
907 let mut result = 0u64;
908 while mask != 0 {
909 let bit = mask & mask.wrapping_neg();
910 if source & 1 != 0 {
911 result |= bit;
912 }
913 source >>= 1;
914 mask &= mask - 1;
915 }
916 result
917 }
918
919 #[test]
920 fn portable_deposit_matches_reference() {
921 deposit_matches_reference::<Portable>();
922 deposit_matches_reference::<PortableLoop>();
923 }
924
925 fn deposit_matches_reference<D: DepositBits>() {
926 let mut state = 0x9E37_79B9_7F4A_7C15;
927 for round in 0..200_000 {
928 let mut mask = xorshift(&mut state);
931 for _ in 0..round % 7 {
932 mask &= xorshift(&mut state);
933 }
934 if round % 2 == 1 {
935 mask = !mask;
936 }
937 let source = xorshift(&mut state);
938 let count = mask.count_ones() as usize;
939 assert_eq!(
940 D::deposit_bits(source, mask, count),
941 deposit_reference(source, mask),
942 "source {source:#018x}, mask {mask:#018x}"
943 );
944 }
945 }
946
947 #[cfg(target_arch = "aarch64")]
948 #[test]
949 fn sve2_bdep_matches_reference() {
950 if !std::arch::is_aarch64_feature_detected!("sve2-bitperm") {
951 return;
952 }
953 deposit_matches_reference::<Sve2Bdep>();
954 for offset in [0, 3, 67] {
955 kernels_match_reference::<Sve2Bdep>(offset);
956 }
957 let mut state = 0x9E37_79B9_7F4A_7C15;
958 for _ in 0..10_000 {
959 let word = xorshift(&mut state) | 1;
960 for rank in 0..word.count_ones() as usize {
961 let bit = deposit_reference(1u64 << rank, word).trailing_zeros() as usize;
962 assert_eq!(Sve2Bdep::select_bit_position(word, rank), bit);
963 }
964 }
965 }
966
967 #[rstest]
969 #[case::aligned(0)]
970 #[case::offset(3)]
971 #[case::past_a_word(67)]
972 fn portable_kernels_match_reference(#[case] offset: usize) {
973 kernels_match_reference::<Portable>(offset);
974 kernels_match_reference::<PortableLoop>(offset);
975 }
976
977 fn kernels_match_reference<D: DepositBits>(offset: usize) {
980 let mut state = 0x2545_F491_4F6C_DD1D;
981 let mut base_source = vec![false; offset];
982 for run in 0..48 {
983 let words = 1 + (xorshift(&mut state) % 20) as usize;
984 for _ in 0..words {
985 let word = match run % 3 {
986 0 => 0,
987 1 => u64::MAX,
988 _ => xorshift(&mut state),
989 };
990 base_source.extend((0..64).map(|i| (word >> i) & 1 == 1));
991 }
992 }
993 base_source.extend([true, false, true, true, false]);
994 let base_len = base_source.len() - offset;
995 let base_bits = base_source[offset..].to_vec();
996 let base_buffer = BitBuffer::from(base_source).slice(offset..offset + base_len);
997
998 let rank_len = base_bits.iter().filter(|&&b| b).count();
999 let rank_bits: Vec<bool> = (0..rank_len)
1000 .map(|_| !xorshift(&mut state).is_multiple_of(3))
1001 .collect();
1002 let mut rank_source = vec![true; offset];
1003 rank_source.extend(&rank_bits);
1004 let rank_buffer = BitBuffer::from(rank_source).slice(offset..offset + rank_len);
1005 let rank_indices: Vec<usize> = rank_bits
1006 .iter()
1007 .enumerate()
1008 .filter_map(|(idx, &value)| value.then_some(idx))
1009 .collect();
1010
1011 let expected = expected_intersect_by_rank(&base_bits, &rank_bits);
1012
1013 assert_eq!(
1014 intersect_bit_buffers::<D>(&base_buffer, &rank_buffer, rank_indices.len()),
1015 expected
1016 );
1017 assert_eq!(
1018 intersect_bit_buffer_by_rank_indices::<D>(&base_buffer, &rank_indices),
1019 expected
1020 );
1021 }
1022
1023 fn expected_intersect_by_rank(base_bits: &[bool], rank_bits: &[bool]) -> Mask {
1024 let mut rank = 0usize;
1025 Mask::from_iter(base_bits.iter().map(|&is_set| {
1026 if is_set {
1027 let keep = rank_bits[rank];
1028 rank += 1;
1029 keep
1030 } else {
1031 false
1032 }
1033 }))
1034 }
1035}