#[inline]
pub fn collect_bool_word_scalar<F>(len: usize, mut f: F) -> u64
where
F: FnMut(usize) -> bool,
{
assert!(len <= 64, "cannot pack {len} bits into a u64 word");
let mut packed = 0;
for bit_idx in 0..len {
packed |= (f(bit_idx) as u64) << bit_idx;
}
packed
}
#[inline(always)]
pub(crate) fn collect_bool_words_inline<F>(words: &mut [u64], len: usize, f: F)
where
F: FnMut(usize) -> bool,
{
#[cfg(all(
target_arch = "x86_64",
target_feature = "avx512f",
target_feature = "avx512bw",
not(miri)
))]
{
collect_bool_words_with(words, len, f, |bools| unsafe {
pack_bool_word_avx512(bools)
})
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "avx2",
not(all(target_feature = "avx512f", target_feature = "avx512bw")),
not(miri)
))]
{
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
}
#[cfg(all(target_arch = "x86_64", not(target_feature = "avx2"), not(miri)))]
{
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
}
#[cfg(all(target_arch = "aarch64", not(miri)))]
{
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
}
#[cfg(any(not(any(target_arch = "x86_64", target_arch = "aarch64")), miri))]
collect_bool_words_with(words, len, f, pack_bool_word_swar)
}
#[inline]
pub fn collect_bool_words_multiversioned<F>(words: &mut [u64], len: usize, f: F)
where
F: FnMut(usize) -> bool,
{
let num_words = len.div_ceil(64);
assert!(
words.len() >= num_words,
"words slice has {} entries, need at least {num_words}",
words.len(),
);
if len < 64 {
return collect_bool_words_inline(words, len, f);
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
{
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw") {
return unsafe { collect_bool_words_avx512(words, len, f) };
}
if is_x86_feature_detected!("avx2") {
return unsafe { collect_bool_words_avx2(words, len, f) };
}
}
collect_bool_words_inline(words, len, f)
}
#[inline(always)]
fn collect_bool_words_with<F, P>(words: &mut [u64], len: usize, mut f: F, pack: P)
where
F: FnMut(usize) -> bool,
P: Fn(&[bool; 64]) -> u64,
{
let full = len / 64;
let remainder = len % 64;
for (word_idx, word) in words[..full].iter_mut().enumerate() {
let offset = word_idx * 64;
let mut bools = [false; 64];
for (bit_idx, b) in bools.iter_mut().enumerate() {
*b = f(offset + bit_idx);
}
*word = pack(&bools);
}
if remainder != 0 {
let offset = full * 64;
words[full] = collect_bool_word_scalar(remainder, |bit_idx| f(offset + bit_idx));
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")]
pub unsafe fn collect_bool_words_sse2<F: FnMut(usize) -> bool>(
words: &mut [u64],
len: usize,
f: F,
) {
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_sse2(bools) })
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn collect_bool_words_avx2<F: FnMut(usize) -> bool>(
words: &mut [u64],
len: usize,
f: F,
) {
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_avx2(bools) })
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f,avx512bw")]
pub unsafe fn collect_bool_words_avx512<F: FnMut(usize) -> bool>(
words: &mut [u64],
len: usize,
f: F,
) {
collect_bool_words_with(words, len, f, |bools| unsafe {
pack_bool_word_avx512(bools)
})
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub unsafe fn collect_bool_words_neon<F: FnMut(usize) -> bool>(
words: &mut [u64],
len: usize,
f: F,
) {
collect_bool_words_with(words, len, f, |bools| unsafe { pack_bool_word_neon(bools) })
}
#[inline]
pub fn pack_bool_word_swar(bools: &[bool; 64]) -> u64 {
const MAGIC: u64 = 0x0102_0408_1020_4080;
let (chunks, rest) = bools.as_chunks::<8>();
debug_assert!(rest.is_empty());
let mut packed = 0u64;
for (chunk_idx, chunk) in chunks.iter().enumerate() {
let word = u64::from_le_bytes(chunk.map(|b| b as u8));
packed |= (word.wrapping_mul(MAGIC) >> 56) << (8 * chunk_idx);
}
packed
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "sse2")]
pub unsafe fn pack_bool_word_sse2(bools: &[bool; 64]) -> u64 {
use std::arch::x86_64::__m128i;
use std::arch::x86_64::_mm_cmpeq_epi8;
use std::arch::x86_64::_mm_loadu_si128;
use std::arch::x86_64::_mm_movemask_epi8;
use std::arch::x86_64::_mm_setzero_si128;
let ptr = bools.as_ptr().cast::<u8>();
let zero = _mm_setzero_si128();
let mut packed = 0u64;
for lane in 0..4 {
let chunk = unsafe { _mm_loadu_si128(ptr.add(lane * 16).cast::<__m128i>()) };
let zero_mask = _mm_movemask_epi8(_mm_cmpeq_epi8(chunk, zero)) as u32 as u64;
packed |= (!zero_mask & 0xFFFF) << (16 * lane);
}
packed
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "avx2")]
pub unsafe fn pack_bool_word_avx2(bools: &[bool; 64]) -> u64 {
use std::arch::x86_64::__m256i;
use std::arch::x86_64::_mm256_cmpeq_epi8;
use std::arch::x86_64::_mm256_loadu_si256;
use std::arch::x86_64::_mm256_movemask_epi8;
use std::arch::x86_64::_mm256_setzero_si256;
let ptr = bools.as_ptr().cast::<u8>();
let zero = _mm256_setzero_si256();
let lo = unsafe { _mm256_loadu_si256(ptr.cast::<__m256i>()) };
let hi = unsafe { _mm256_loadu_si256(ptr.add(32).cast::<__m256i>()) };
let lo_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(lo, zero)) as u32) as u64;
let hi_mask = !(_mm256_movemask_epi8(_mm256_cmpeq_epi8(hi, zero)) as u32) as u64;
lo_mask | (hi_mask << 32)
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "avx512f,avx512bw")]
pub unsafe fn pack_bool_word_avx512(bools: &[bool; 64]) -> u64 {
use std::arch::x86_64::__m512i;
use std::arch::x86_64::_mm512_loadu_si512;
use std::arch::x86_64::_mm512_test_epi8_mask;
let chunk = unsafe { _mm512_loadu_si512(bools.as_ptr().cast::<__m512i>()) };
_mm512_test_epi8_mask(chunk, chunk)
}
#[cfg(target_arch = "aarch64")]
#[inline]
#[target_feature(enable = "neon")]
pub unsafe fn pack_bool_word_neon(bools: &[bool; 64]) -> u64 {
use std::arch::aarch64::vgetq_lane_u64;
use std::arch::aarch64::vld1q_s8;
use std::arch::aarch64::vld1q_u8;
use std::arch::aarch64::vpaddq_u8;
use std::arch::aarch64::vreinterpretq_u64_u8;
use std::arch::aarch64::vshlq_u8;
const BIT_SHIFTS: [i8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
let ptr = bools.as_ptr().cast::<u8>();
unsafe {
let shifts = vld1q_s8(BIT_SHIFTS.as_ptr());
let m0 = vshlq_u8(vld1q_u8(ptr), shifts);
let m1 = vshlq_u8(vld1q_u8(ptr.add(16)), shifts);
let m2 = vshlq_u8(vld1q_u8(ptr.add(32)), shifts);
let m3 = vshlq_u8(vld1q_u8(ptr.add(48)), shifts);
let sum01 = vpaddq_u8(m0, m1);
let sum23 = vpaddq_u8(m2, m3);
let sum = vpaddq_u8(sum01, sum23);
let sum = vpaddq_u8(sum, sum);
vgetq_lane_u64::<0>(vreinterpretq_u64_u8(sum))
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::collect_bool_word_scalar;
use super::pack_bool_word_swar;
fn patterns() -> Vec<[bool; 64]> {
let mut patterns = vec![
[false; 64],
[true; 64],
std::array::from_fn(|i| i % 2 == 0),
std::array::from_fn(|i| i % 3 == 0),
std::array::from_fn(|i| i < 32),
std::array::from_fn(|i| i == 0 || i == 63),
];
let mut state = 0x9E37_79B9_7F4A_7C15u64;
for _ in 0..8 {
patterns.push(std::array::from_fn(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 33) & 1 == 1
}));
}
patterns
}
fn reference(bools: &[bool; 64]) -> u64 {
collect_bool_word_scalar(64, |i| bools[i])
}
#[test]
fn swar_matches_scalar() {
for bools in patterns() {
assert_eq!(pack_bool_word_swar(&bools), reference(&bools));
}
}
#[test]
fn dispatch_matches_scalar() {
for bools in patterns() {
assert_eq!(
crate::bit::collect_bool_word(64, |i| bools[i]),
reference(&bools)
);
}
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn sse2_matches_scalar() {
for bools in patterns() {
assert_eq!(
unsafe { super::pack_bool_word_sse2(&bools) },
reference(&bools)
);
}
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn avx2_matches_scalar() {
if !is_x86_feature_detected!("avx2") {
return;
}
for bools in patterns() {
assert_eq!(
unsafe { super::pack_bool_word_avx2(&bools) },
reference(&bools)
);
}
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn avx512_matches_scalar() {
if !(is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw")) {
return;
}
for bools in patterns() {
assert_eq!(
unsafe { super::pack_bool_word_avx512(&bools) },
reference(&bools)
);
}
}
#[cfg(all(target_arch = "aarch64", not(miri)))]
#[test]
fn neon_matches_scalar() {
for bools in patterns() {
assert_eq!(
unsafe { super::pack_bool_word_neon(&bools) },
reference(&bools)
);
}
}
#[rstest]
#[case(0)]
#[case(1)]
#[case(63)]
#[case(64)]
#[case(65)]
#[case(200)]
fn multiversioned_matches_inline(#[case] len: usize) {
let pattern = |i: usize| i.is_multiple_of(3) || i.is_multiple_of(7);
let num_words = len.div_ceil(64);
let mut multiversioned = vec![0u64; num_words];
super::collect_bool_words_multiversioned(&mut multiversioned, len, pattern);
let mut inline = vec![0u64; num_words];
super::collect_bool_words_inline(&mut inline, len, pattern);
assert_eq!(multiversioned, inline);
}
#[rstest]
#[case(0)]
#[case(1)]
#[case(5)]
#[case(63)]
#[case(64)]
fn collect_bool_word_partial_lens_match(#[case] len: usize) {
let expected = collect_bool_word_scalar(len, |i| i % 3 == 0);
assert_eq!(crate::bit::collect_bool_word(len, |i| i % 3 == 0), expected);
}
}