1#[inline]
33pub fn collect_bool_word_scalar<F>(len: usize, mut f: F) -> u64
34where
35 F: FnMut(usize) -> bool,
36{
37 assert!(len <= 64, "cannot pack {len} bits into a u64 word");
38
39 let mut packed = 0;
40 for bit_idx in 0..len {
41 packed |= (f(bit_idx) as u64) << bit_idx;
42 }
43 packed
44}
45
46#[inline(always)]
60pub(crate) fn collect_bool_words_inline<F>(words: &mut [u64], len: usize, f: F)
61where
62 F: FnMut(usize) -> bool,
63{
64 #[cfg(all(
65 target_arch = "x86_64",
66 target_feature = "avx512f",
67 target_feature = "avx512bw",
68 not(miri)
69 ))]
70 {
71 collect_bool_words_with(words, len, f, |bools| unsafe {
75 pack_bool_word_avx512(bools)
76 })
77 }
78 #[cfg(all(
79 target_arch = "x86_64",
80 target_feature = "avx2",
81 not(all(target_feature = "avx512f", target_feature = "avx512bw")),
82 not(miri)
83 ))]
84 {
85 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
87 }
88 #[cfg(all(target_arch = "x86_64", not(target_feature = "avx2"), not(miri)))]
89 {
90 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
92 }
93 #[cfg(all(target_arch = "aarch64", not(miri)))]
94 {
95 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
97 }
98 #[cfg(any(not(any(target_arch = "x86_64", target_arch = "aarch64")), miri))]
99 collect_bool_words_with(words, len, f, pack_bool_word_swar)
100}
101
102#[inline]
117pub fn collect_bool_words_multiversioned<F>(words: &mut [u64], len: usize, f: F)
118where
119 F: FnMut(usize) -> bool,
120{
121 let num_words = len.div_ceil(64);
122 assert!(
123 words.len() >= num_words,
124 "words slice has {} entries, need at least {num_words}",
125 words.len(),
126 );
127
128 if len < 64 {
131 return collect_bool_words_inline(words, len, f);
132 }
133
134 #[cfg(all(target_arch = "x86_64", not(miri)))]
135 {
136 if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw") {
137 return unsafe { collect_bool_words_avx512(words, len, f) };
139 }
140 if is_x86_feature_detected!("avx2") {
141 return unsafe { collect_bool_words_avx2(words, len, f) };
143 }
144 }
145 collect_bool_words_inline(words, len, f)
146}
147
148#[inline(always)]
154fn collect_bool_words_with<F, P>(words: &mut [u64], len: usize, mut f: F, pack: P)
155where
156 F: FnMut(usize) -> bool,
157 P: Fn(&[bool; 64]) -> u64,
158{
159 let full = len / 64;
160 let remainder = len % 64;
161
162 for (word_idx, word) in words[..full].iter_mut().enumerate() {
163 let offset = word_idx * 64;
164 let mut bools = [false; 64];
165 for (bit_idx, b) in bools.iter_mut().enumerate() {
166 *b = f(offset + bit_idx);
167 }
168 *word = pack(&bools);
169 }
170
171 if remainder != 0 {
172 let offset = full * 64;
173 words[full] = collect_bool_word_scalar(remainder, |bit_idx| f(offset + bit_idx));
174 }
175}
176
177#[cfg(target_arch = "x86_64")]
185#[target_feature(enable = "sse2")]
186pub unsafe fn collect_bool_words_sse2<F: FnMut(usize) -> bool>(
187 words: &mut [u64],
188 len: usize,
189 f: F,
190) {
191 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
193}
194
195#[cfg(target_arch = "x86_64")]
203#[target_feature(enable = "avx2")]
204pub unsafe fn collect_bool_words_avx2<F: FnMut(usize) -> bool>(
205 words: &mut [u64],
206 len: usize,
207 f: F,
208) {
209 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
211}
212
213#[cfg(target_arch = "x86_64")]
221#[target_feature(enable = "avx512f,avx512bw")]
222pub unsafe fn collect_bool_words_avx512<F: FnMut(usize) -> bool>(
223 words: &mut [u64],
224 len: usize,
225 f: F,
226) {
227 collect_bool_words_with(words, len, f, |bools| unsafe {
229 pack_bool_word_avx512(bools)
230 })
231}
232
233#[cfg(target_arch = "aarch64")]
241#[target_feature(enable = "neon")]
242pub unsafe fn collect_bool_words_neon<F: FnMut(usize) -> bool>(
243 words: &mut [u64],
244 len: usize,
245 f: F,
246) {
247 collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
249}
250
251#[inline]
258pub fn pack_bool_word_swar(bools: &[bool; 64]) -> u64 {
259 const MAGIC: u64 = 0x0102_0408_1020_4080;
260
261 let (chunks, rest) = bools.as_chunks::<8>();
262 debug_assert!(rest.is_empty());
263
264 let mut packed = 0u64;
265 for (chunk_idx, chunk) in chunks.iter().enumerate() {
266 let word = u64::from_le_bytes(chunk.map(|b| b as u8));
267 packed |= (word.wrapping_mul(MAGIC) >> 56) << (8 * chunk_idx);
268 }
269 packed
270}
271
272#[cfg(target_arch = "x86_64")]
278#[inline]
279#[target_feature(enable = "sse2")]
280pub unsafe fn pack_bool_word_sse2(bools: &[bool; 64]) -> u64 {
281 use std::arch::x86_64::__m128i;
282 use std::arch::x86_64::_mm_cmpeq_epi8;
283 use std::arch::x86_64::_mm_loadu_si128;
284 use std::arch::x86_64::_mm_movemask_epi8;
285 use std::arch::x86_64::_mm_setzero_si128;
286
287 let ptr = bools.as_ptr().cast::<u8>();
288 let zero = _mm_setzero_si128();
289
290 let mut packed = 0u64;
291 for lane in 0..4 {
292 let chunk = unsafe { _mm_loadu_si128(ptr.add(lane * 16).cast::<__m128i>()) };
294 let zero_mask = _mm_movemask_epi8(_mm_cmpeq_epi8(chunk, zero)) as u32 as u64;
296 packed |= (!zero_mask & 0xFFFF) << (16 * lane);
297 }
298 packed
299}
300
301#[cfg(target_arch = "x86_64")]
307#[inline]
308#[target_feature(enable = "avx2")]
309pub unsafe fn pack_bool_word_avx2(bools: &[bool; 64]) -> u64 {
310 use std::arch::x86_64::__m256i;
311 use std::arch::x86_64::_mm256_cmpeq_epi8;
312 use std::arch::x86_64::_mm256_loadu_si256;
313 use std::arch::x86_64::_mm256_movemask_epi8;
314 use std::arch::x86_64::_mm256_setzero_si256;
315
316 let ptr = bools.as_ptr().cast::<u8>();
317 let zero = _mm256_setzero_si256();
318
319 let lo = unsafe { _mm256_loadu_si256(ptr.cast::<__m256i>()) };
321 let hi = unsafe { _mm256_loadu_si256(ptr.add(32).cast::<__m256i>()) };
323
324 let lo_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(lo, zero)) as u32) as u64;
326 let hi_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(hi, zero)) as u32) as u64;
327 lo_mask | (hi_mask << 32)
328}
329
330#[cfg(target_arch = "x86_64")]
336#[inline]
337#[target_feature(enable = "avx512f,avx512bw")]
338pub unsafe fn pack_bool_word_avx512(bools: &[bool; 64]) -> u64 {
339 use std::arch::x86_64::__m512i;
340 use std::arch::x86_64::_mm512_loadu_si512;
341 use std::arch::x86_64::_mm512_test_epi8_mask;
342
343 let chunk = unsafe { _mm512_loadu_si512(bools.as_ptr().cast::<__m512i>()) };
345 _mm512_test_epi8_mask(chunk, chunk)
347}
348
349#[cfg(target_arch = "aarch64")]
356#[inline]
357#[target_feature(enable = "neon")]
358pub unsafe fn pack_bool_word_neon(bools: &[bool; 64]) -> u64 {
359 use std::arch::aarch64::vgetq_lane_u64;
360 use std::arch::aarch64::vld1q_s8;
361 use std::arch::aarch64::vld1q_u8;
362 use std::arch::aarch64::vpaddq_u8;
363 use std::arch::aarch64::vreinterpretq_u64_u8;
364 use std::arch::aarch64::vshlq_u8;
365
366 const BIT_SHIFTS: [i8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
367
368 let ptr = bools.as_ptr().cast::<u8>();
369 unsafe {
372 let shifts = vld1q_s8(BIT_SHIFTS.as_ptr());
373
374 let m0 = vshlq_u8(vld1q_u8(ptr), shifts);
376 let m1 = vshlq_u8(vld1q_u8(ptr.add(16)), shifts);
377 let m2 = vshlq_u8(vld1q_u8(ptr.add(32)), shifts);
378 let m3 = vshlq_u8(vld1q_u8(ptr.add(48)), shifts);
379
380 let sum01 = vpaddq_u8(m0, m1);
383 let sum23 = vpaddq_u8(m2, m3);
384 let sum = vpaddq_u8(sum01, sum23);
385 let sum = vpaddq_u8(sum, sum);
386 vgetq_lane_u64::<0>(vreinterpretq_u64_u8(sum))
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use rstest::rstest;
393
394 use super::collect_bool_word_scalar;
395 use super::pack_bool_word_swar;
396
397 fn patterns() -> Vec<[bool; 64]> {
398 let mut patterns = vec![
399 [false; 64],
400 [true; 64],
401 std::array::from_fn(|i| i % 2 == 0),
402 std::array::from_fn(|i| i % 3 == 0),
403 std::array::from_fn(|i| i < 32),
404 std::array::from_fn(|i| i == 0 || i == 63),
405 ];
406 let mut state = 0x9E37_79B9_7F4A_7C15u64;
408 for _ in 0..8 {
409 patterns.push(std::array::from_fn(|_| {
410 state = state
411 .wrapping_mul(6364136223846793005)
412 .wrapping_add(1442695040888963407);
413 (state >> 33) & 1 == 1
414 }));
415 }
416 patterns
417 }
418
419 fn reference(bools: &[bool; 64]) -> u64 {
420 collect_bool_word_scalar(64, |i| bools[i])
421 }
422
423 #[test]
424 fn swar_matches_scalar() {
425 for bools in patterns() {
426 assert_eq!(pack_bool_word_swar(&bools), reference(&bools));
427 }
428 }
429
430 #[test]
431 fn dispatch_matches_scalar() {
432 for bools in patterns() {
433 assert_eq!(
434 crate::bit::collect_bool_word(64, |i| bools[i]),
435 reference(&bools)
436 );
437 }
438 }
439
440 #[cfg(all(target_arch = "x86_64", not(miri)))]
441 #[test]
442 fn sse2_matches_scalar() {
443 for bools in patterns() {
444 assert_eq!(
446 unsafe { super::pack_bool_word_sse2(&bools) },
447 reference(&bools)
448 );
449 }
450 }
451
452 #[cfg(all(target_arch = "x86_64", not(miri)))]
453 #[test]
454 fn avx2_matches_scalar() {
455 if !is_x86_feature_detected!("avx2") {
456 return;
457 }
458 for bools in patterns() {
459 assert_eq!(
461 unsafe { super::pack_bool_word_avx2(&bools) },
462 reference(&bools)
463 );
464 }
465 }
466
467 #[cfg(all(target_arch = "x86_64", not(miri)))]
468 #[test]
469 fn avx512_matches_scalar() {
470 if !(is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw")) {
471 return;
472 }
473 for bools in patterns() {
474 assert_eq!(
476 unsafe { super::pack_bool_word_avx512(&bools) },
477 reference(&bools)
478 );
479 }
480 }
481
482 #[cfg(all(target_arch = "aarch64", not(miri)))]
483 #[test]
484 fn neon_matches_scalar() {
485 for bools in patterns() {
486 assert_eq!(
488 unsafe { super::pack_bool_word_neon(&bools) },
489 reference(&bools)
490 );
491 }
492 }
493
494 #[rstest]
495 #[case(0)]
496 #[case(1)]
497 #[case(63)]
498 #[case(64)]
499 #[case(65)]
500 #[case(200)]
501 fn multiversioned_matches_inline(#[case] len: usize) {
502 let pattern = |i: usize| i.is_multiple_of(3) || i.is_multiple_of(7);
503 let num_words = len.div_ceil(64);
504 let mut multiversioned = vec![0u64; num_words];
505 super::collect_bool_words_multiversioned(&mut multiversioned, len, pattern);
506 let mut inline = vec![0u64; num_words];
507 super::collect_bool_words_inline(&mut inline, len, pattern);
508 assert_eq!(multiversioned, inline);
509 }
510
511 #[rstest]
512 #[case(0)]
513 #[case(1)]
514 #[case(5)]
515 #[case(63)]
516 #[case(64)]
517 fn collect_bool_word_partial_lens_match(#[case] len: usize) {
518 let expected = collect_bool_word_scalar(len, |i| i % 3 == 0);
519 assert_eq!(crate::bit::collect_bool_word(len, |i| i % 3 == 0), expected);
520 }
521}