1#[inline]
34pub fn collect_bool_word_scalar<F>(len: usize, mut f: F) -> u64
35where
36 F: FnMut(usize) -> bool,
37{
38 assert!(len <= 64, "cannot pack {len} bits into a u64 word");
39
40 let mut packed = 0;
41 for bit_idx in 0..len {
42 packed |= (f(bit_idx) as u64) << bit_idx;
43 }
44 packed
45}
46
47#[allow(clippy::inline_always)]
61#[inline(always)]
62pub(crate) fn collect_bool_words_inline<F>(words: &mut [u64], len: usize, f: F)
63where
64 F: FnMut(usize) -> bool,
65{
66 #[cfg(all(
67 target_arch = "x86_64",
68 target_feature = "avx512f",
69 target_feature = "avx512bw",
70 not(miri)
71 ))]
72 {
73 collect_bool_words_with(words, len, f, |bools| unsafe {
77 pack_bool_word_avx512(bools)
78 })
79 }
80 #[cfg(all(
81 target_arch = "x86_64",
82 target_feature = "avx2",
83 not(all(target_feature = "avx512f", target_feature = "avx512bw")),
84 not(miri)
85 ))]
86 {
87 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
89 }
90 #[cfg(all(target_arch = "x86_64", not(target_feature = "avx2"), not(miri)))]
91 {
92 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
94 }
95 #[cfg(all(target_arch = "aarch64", not(miri)))]
96 {
97 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
99 }
100 #[cfg(any(not(any(target_arch = "x86_64", target_arch = "aarch64")), miri))]
101 collect_bool_words_with(words, len, f, pack_bool_word_swar)
102}
103
104#[inline]
119pub fn collect_bool_words_multiversioned<F>(words: &mut [u64], len: usize, f: F)
120where
121 F: FnMut(usize) -> bool,
122{
123 let num_words = len.div_ceil(64);
124 assert!(
125 words.len() >= num_words,
126 "words slice has {} entries, need at least {num_words}",
127 words.len(),
128 );
129
130 if len < 64 {
133 return collect_bool_words_inline(words, len, f);
134 }
135
136 #[cfg(all(target_arch = "x86_64", not(miri)))]
137 {
138 if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw") {
139 return unsafe { collect_bool_words_avx512(words, len, f) };
141 }
142 if is_x86_feature_detected!("avx2") {
143 return unsafe { collect_bool_words_avx2(words, len, f) };
145 }
146 }
147 collect_bool_words_inline(words, len, f)
148}
149
150#[allow(clippy::inline_always)]
156#[inline(always)]
157fn collect_bool_words_with<F, P>(words: &mut [u64], len: usize, mut f: F, pack: P)
158where
159 F: FnMut(usize) -> bool,
160 P: Fn(&[bool; 64]) -> u64,
161{
162 let full = len / 64;
163 let remainder = len % 64;
164
165 for (word_idx, word) in words[..full].iter_mut().enumerate() {
166 let offset = word_idx * 64;
167 let mut bools = [false; 64];
168 for (bit_idx, b) in bools.iter_mut().enumerate() {
169 *b = f(offset + bit_idx);
170 }
171 *word = pack(&bools);
172 }
173
174 if remainder != 0 {
175 let offset = full * 64;
176 words[full] = collect_bool_word_tail(remainder, |bit_idx| f(offset + bit_idx));
177 }
178}
179
180#[expect(clippy::inline_always)]
188#[inline(always)]
189fn collect_bool_word_tail<F>(len: usize, mut f: F) -> u64
190where
191 F: FnMut(usize) -> bool,
192{
193 debug_assert!(len <= 64, "cannot pack {len} bits into a u64 word");
194
195 let mut packed = 0;
196 for bit_idx in 0..len {
197 packed |= (f(bit_idx) as u64) << bit_idx;
198 }
199 packed
200}
201
202#[cfg(target_arch = "x86_64")]
210#[target_feature(enable = "sse2")]
211pub unsafe fn collect_bool_words_sse2<F: FnMut(usize) -> bool>(
212 words: &mut [u64],
213 len: usize,
214 f: F,
215) {
216 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
218}
219
220#[cfg(target_arch = "x86_64")]
228#[target_feature(enable = "avx2")]
229pub unsafe fn collect_bool_words_avx2<F: FnMut(usize) -> bool>(
230 words: &mut [u64],
231 len: usize,
232 f: F,
233) {
234 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
236}
237
238#[cfg(target_arch = "x86_64")]
246#[target_feature(enable = "avx512f,avx512bw")]
247pub unsafe fn collect_bool_words_avx512<F: FnMut(usize) -> bool>(
248 words: &mut [u64],
249 len: usize,
250 f: F,
251) {
252 collect_bool_words_with(words, len, f, |bools| unsafe {
254 pack_bool_word_avx512(bools)
255 })
256}
257
258#[cfg(target_arch = "aarch64")]
266#[target_feature(enable = "neon")]
267pub unsafe fn collect_bool_words_neon<F: FnMut(usize) -> bool>(
268 words: &mut [u64],
269 len: usize,
270 f: F,
271) {
272 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
274}
275
276#[inline]
283pub fn pack_bool_word_swar(bools: &[bool; 64]) -> u64 {
284 const MAGIC: u64 = 0x0102_0408_1020_4080;
285
286 let (chunks, rest) = bools.as_chunks::<8>();
287 debug_assert!(rest.is_empty());
288
289 let mut packed = 0u64;
290 for (chunk_idx, chunk) in chunks.iter().enumerate() {
291 let word = u64::from_le_bytes(chunk.map(|b| b as u8));
292 packed |= (word.wrapping_mul(MAGIC) >> 56) << (8 * chunk_idx);
293 }
294 packed
295}
296
297#[cfg(target_arch = "x86_64")]
303#[inline]
304#[target_feature(enable = "sse2")]
305pub unsafe fn pack_bool_word_sse2(bools: &[bool; 64]) -> u64 {
306 use std::arch::x86_64::__m128i;
307 use std::arch::x86_64::_mm_cmpeq_epi8;
308 use std::arch::x86_64::_mm_loadu_si128;
309 use std::arch::x86_64::_mm_movemask_epi8;
310 use std::arch::x86_64::_mm_setzero_si128;
311
312 let ptr = bools.as_ptr().cast::<u8>();
313 let zero = _mm_setzero_si128();
314
315 let mut packed = 0u64;
316 for lane in 0..4 {
317 let chunk = unsafe { _mm_loadu_si128(ptr.add(lane * 16).cast::<__m128i>()) };
319 let zero_mask = _mm_movemask_epi8(_mm_cmpeq_epi8(chunk, zero)) as u32 as u64;
321 packed |= (!zero_mask & 0xFFFF) << (16 * lane);
322 }
323 packed
324}
325
326#[cfg(target_arch = "x86_64")]
332#[inline]
333#[target_feature(enable = "avx2")]
334pub unsafe fn pack_bool_word_avx2(bools: &[bool; 64]) -> u64 {
335 use std::arch::x86_64::__m256i;
336 use std::arch::x86_64::_mm256_cmpeq_epi8;
337 use std::arch::x86_64::_mm256_loadu_si256;
338 use std::arch::x86_64::_mm256_movemask_epi8;
339 use std::arch::x86_64::_mm256_setzero_si256;
340
341 let ptr = bools.as_ptr().cast::<u8>();
342 let zero = _mm256_setzero_si256();
343
344 let lo = unsafe { _mm256_loadu_si256(ptr.cast::<__m256i>()) };
346 let hi = unsafe { _mm256_loadu_si256(ptr.add(32).cast::<__m256i>()) };
348
349 let lo_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(lo, zero)) as u32) as u64;
351 let hi_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(hi, zero)) as u32) as u64;
352 lo_mask | (hi_mask << 32)
353}
354
355#[cfg(target_arch = "x86_64")]
361#[inline]
362#[target_feature(enable = "avx512f,avx512bw")]
363pub unsafe fn pack_bool_word_avx512(bools: &[bool; 64]) -> u64 {
364 use std::arch::x86_64::__m512i;
365 use std::arch::x86_64::_mm512_loadu_si512;
366 use std::arch::x86_64::_mm512_test_epi8_mask;
367
368 let chunk = unsafe { _mm512_loadu_si512(bools.as_ptr().cast::<__m512i>()) };
370 _mm512_test_epi8_mask(chunk, chunk)
372}
373
374#[cfg(target_arch = "aarch64")]
381#[inline]
382#[target_feature(enable = "neon")]
383pub unsafe fn pack_bool_word_neon(bools: &[bool; 64]) -> u64 {
384 use std::arch::aarch64::vgetq_lane_u64;
385 use std::arch::aarch64::vld1q_s8;
386 use std::arch::aarch64::vld1q_u8;
387 use std::arch::aarch64::vpaddq_u8;
388 use std::arch::aarch64::vreinterpretq_u64_u8;
389 use std::arch::aarch64::vshlq_u8;
390
391 const BIT_SHIFTS: [i8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
392
393 let ptr = bools.as_ptr().cast::<u8>();
394 unsafe {
397 let shifts = vld1q_s8(BIT_SHIFTS.as_ptr());
398
399 let m0 = vshlq_u8(vld1q_u8(ptr), shifts);
401 let m1 = vshlq_u8(vld1q_u8(ptr.add(16)), shifts);
402 let m2 = vshlq_u8(vld1q_u8(ptr.add(32)), shifts);
403 let m3 = vshlq_u8(vld1q_u8(ptr.add(48)), shifts);
404
405 let sum01 = vpaddq_u8(m0, m1);
408 let sum23 = vpaddq_u8(m2, m3);
409 let sum = vpaddq_u8(sum01, sum23);
410 let sum = vpaddq_u8(sum, sum);
411 vgetq_lane_u64::<0>(vreinterpretq_u64_u8(sum))
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use rstest::rstest;
418
419 use super::collect_bool_word_scalar;
420 use super::pack_bool_word_swar;
421
422 fn patterns() -> Vec<[bool; 64]> {
423 let mut patterns = vec![
424 [false; 64],
425 [true; 64],
426 std::array::from_fn(|i| i % 2 == 0),
427 std::array::from_fn(|i| i % 3 == 0),
428 std::array::from_fn(|i| i < 32),
429 std::array::from_fn(|i| i == 0 || i == 63),
430 ];
431 let mut state = 0x9E37_79B9_7F4A_7C15u64;
433 for _ in 0..8 {
434 patterns.push(std::array::from_fn(|_| {
435 state = state
436 .wrapping_mul(6364136223846793005)
437 .wrapping_add(1442695040888963407);
438 (state >> 33) & 1 == 1
439 }));
440 }
441 patterns
442 }
443
444 fn reference(bools: &[bool; 64]) -> u64 {
445 collect_bool_word_scalar(64, |i| bools[i])
446 }
447
448 #[test]
449 fn swar_matches_scalar() {
450 for bools in patterns() {
451 assert_eq!(pack_bool_word_swar(&bools), reference(&bools));
452 }
453 }
454
455 #[test]
456 fn dispatch_matches_scalar() {
457 for bools in patterns() {
458 assert_eq!(
459 crate::bit::collect_bool_word(64, |i| bools[i]),
460 reference(&bools)
461 );
462 }
463 }
464
465 #[cfg(all(target_arch = "x86_64", not(miri)))]
466 #[test]
467 fn sse2_matches_scalar() {
468 for bools in patterns() {
469 assert_eq!(
471 unsafe { super::pack_bool_word_sse2(&bools) },
472 reference(&bools)
473 );
474 }
475 }
476
477 #[cfg(all(target_arch = "x86_64", not(miri)))]
478 #[test]
479 fn avx2_matches_scalar() {
480 if !is_x86_feature_detected!("avx2") {
481 return;
482 }
483 for bools in patterns() {
484 assert_eq!(
486 unsafe { super::pack_bool_word_avx2(&bools) },
487 reference(&bools)
488 );
489 }
490 }
491
492 #[cfg(all(target_arch = "x86_64", not(miri)))]
493 #[test]
494 fn avx512_matches_scalar() {
495 if !(is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw")) {
496 return;
497 }
498 for bools in patterns() {
499 assert_eq!(
501 unsafe { super::pack_bool_word_avx512(&bools) },
502 reference(&bools)
503 );
504 }
505 }
506
507 #[cfg(all(target_arch = "aarch64", not(miri)))]
508 #[test]
509 fn neon_matches_scalar() {
510 for bools in patterns() {
511 assert_eq!(
513 unsafe { super::pack_bool_word_neon(&bools) },
514 reference(&bools)
515 );
516 }
517 }
518
519 #[rstest]
520 #[case(0)]
521 #[case(1)]
522 #[case(63)]
523 #[case(64)]
524 #[case(65)]
525 #[case(200)]
526 fn multiversioned_matches_inline(#[case] len: usize) {
527 let pattern = |i: usize| i.is_multiple_of(3) || i.is_multiple_of(7);
528 let num_words = len.div_ceil(64);
529 let mut multiversioned = vec![0u64; num_words];
530 super::collect_bool_words_multiversioned(&mut multiversioned, len, pattern);
531 let mut inline = vec![0u64; num_words];
532 super::collect_bool_words_inline(&mut inline, len, pattern);
533 assert_eq!(multiversioned, inline);
534 }
535
536 #[rstest]
537 #[case(0)]
538 #[case(1)]
539 #[case(5)]
540 #[case(63)]
541 #[case(64)]
542 fn collect_bool_word_partial_lens_match(#[case] len: usize) {
543 let expected = collect_bool_word_scalar(len, |i| i % 3 == 0);
544 assert_eq!(crate::bit::collect_bool_word(len, |i| i % 3 == 0), expected);
545 }
546}