#[cfg(test)]
#[inline(always)]
#[allow(dead_code)] pub(crate) fn prefetch_read(slice: &[u8], at: usize) {
if at >= slice.len() {
return;
}
#[cfg(target_arch = "x86_64")]
{
unsafe {
core::arch::x86_64::_mm_prefetch(
slice.as_ptr().add(at) as *const i8,
core::arch::x86_64::_MM_HINT_T0,
);
}
}
#[cfg(not(target_arch = "x86_64"))]
{
let _ = at;
}
}
#[inline(always)]
pub(crate) fn load_u32_le(src: &[u8], i: usize) -> u32 {
debug_assert!(src.len() >= 4 && i <= src.len() - 4);
let arr = unsafe { src.as_ptr().add(i).cast::<[u8; 4]>().read_unaligned() };
u32::from_le_bytes(arr)
}
#[inline(always)]
pub(crate) fn load_u64_le(src: &[u8], i: usize) -> u64 {
debug_assert!(src.len() >= 8 && i <= src.len() - 8);
let arr = unsafe { src.as_ptr().add(i).cast::<[u8; 8]>().read_unaligned() };
u64::from_le_bytes(arr)
}
#[cfg(all(target_arch = "x86_64", any(test, feature = "profile")))]
#[inline]
unsafe fn avx2_with_ladder(a: *const u8, b: *const u8, max: usize) -> usize {
debug_assert!(max >= 64);
unsafe {
if let Some(r) = ladder32(a, b) {
return r;
}
count_eq_len_avx2(a, b, max)
}
}
#[cfg(feature = "profile")]
pub fn bench_eq_avx2(a: &[u8], b: &[u8]) -> usize {
let max = a.len().min(b.len());
#[cfg(all(target_arch = "x86_64", feature = "std"))]
{
if max >= 64 && has_avx2() {
return unsafe { avx2_with_ladder(a.as_ptr(), b.as_ptr(), max) };
}
}
count_eq_len_words(a, b, max)
}
#[cfg(feature = "profile")]
pub fn bench_eq_words(a: &[u8], b: &[u8]) -> usize {
count_eq_len_words(a, b, a.len().min(b.len()))
}
#[cfg(feature = "profile")]
pub static EQLEN_ARM: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
#[cfg(feature = "profile")]
pub fn set_eqlen_arm(v: u8) {
EQLEN_ARM.store(v, core::sync::atomic::Ordering::Relaxed);
}
#[cfg(feature = "profile")]
#[inline(always)]
fn eqlen_arm() -> u8 {
EQLEN_ARM.load(core::sync::atomic::Ordering::Relaxed)
}
#[cfg(not(feature = "profile"))]
#[inline(always)]
fn eqlen_arm() -> u8 {
0
}
#[cfg(feature = "profile")]
mod counters {
use core::cell::Cell;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
pub(super) static G_CALLS: AtomicU64 = AtomicU64::new(0);
pub(super) static G_WIDE: AtomicU64 = AtomicU64::new(0);
pub(super) static G_OPS: [AtomicU64; 3] =
[AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0)];
pub(super) static G_HIST: [AtomicU64; 5] = [
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
];
pub(super) struct Tls {
pub(super) calls: Cell<u64>,
pub(super) wide: Cell<u64>,
pub(super) ops: [Cell<u64>; 3],
pub(super) hist: [Cell<u64>; 5],
}
fn fold(c: &Cell<u64>, g: &AtomicU64) {
let v = c.replace(0);
if v != 0 {
g.fetch_add(v, Relaxed);
}
}
impl Tls {
const fn new() -> Self {
Tls {
calls: Cell::new(0),
wide: Cell::new(0),
ops: [Cell::new(0), Cell::new(0), Cell::new(0)],
hist: [
Cell::new(0),
Cell::new(0),
Cell::new(0),
Cell::new(0),
Cell::new(0),
],
}
}
fn flush(&self) {
fold(&self.calls, &G_CALLS);
fold(&self.wide, &G_WIDE);
for (c, g) in self.ops.iter().zip(G_OPS.iter()) {
fold(c, g);
}
for (c, g) in self.hist.iter().zip(G_HIST.iter()) {
fold(c, g);
}
}
}
impl Drop for Tls {
fn drop(&mut self) {
self.flush();
}
}
std::thread_local! {
pub(super) static TLS: Tls = const { Tls::new() };
}
pub(super) fn flush_this_thread() {
let _ = TLS.try_with(|t| t.flush());
}
}
#[cfg(feature = "profile")]
#[inline(always)]
fn eq_op_n(kind: usize, n: usize) {
if n == 0 {
return;
}
let _ = counters::TLS.try_with(|t| {
let c = &t.ops[kind];
c.set(c.get() + n as u64);
});
}
#[cfg(not(feature = "profile"))]
#[inline(always)]
fn eq_op_n(_kind: usize, _n: usize) {}
#[cfg(not(feature = "profile"))]
#[inline(always)]
fn eq_call(_wide_eligible: bool) {}
#[cfg(feature = "profile")]
#[inline(always)]
fn eq_call(wide_eligible: bool) {
let _ = counters::TLS.try_with(|t| {
t.calls.set(t.calls.get() + 1);
if wide_eligible {
t.wide.set(t.wide.get() + 1);
}
});
}
#[cfg(feature = "profile")]
pub fn take_eq_ops() -> (u64, u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
counters::flush_this_thread();
(
counters::G_OPS[0].swap(0, Relaxed),
counters::G_OPS[1].swap(0, Relaxed),
counters::G_OPS[2].swap(0, Relaxed),
)
}
#[cfg(feature = "profile")]
pub fn take_eqlen_stats() -> (u64, u64, [u64; 5]) {
use core::sync::atomic::Ordering::Relaxed;
counters::flush_this_thread();
let mut h = [0u64; 5];
for (i, v) in counters::G_HIST.iter().enumerate() {
h[i] = v.swap(0, Relaxed);
}
(
counters::G_CALLS.swap(0, Relaxed),
counters::G_WIDE.swap(0, Relaxed),
h,
)
}
#[cfg(test)]
pub(crate) fn count_eq_len(a: &[u8], b: &[u8]) -> usize {
let max = a.len().min(b.len());
if max < 8 {
let mut n = 0usize;
while n < max && a[n] == b[n] {
n += 1;
}
return n;
}
count_eq_len_ge8(a, b, max)
}
#[inline(always)]
unsafe fn word_ne(a: *const u8, b: *const u8, n: usize) -> Option<usize> {
let (av, bv) = unsafe {
(
core::ptr::read_unaligned(a.add(n) as *const u64),
core::ptr::read_unaligned(b.add(n) as *const u64),
)
};
if av != bv {
Some(n + ((av ^ bv).trailing_zeros() as usize) / 8)
} else {
None
}
}
#[inline(always)]
unsafe fn ladder32(a: *const u8, b: *const u8) -> Option<usize> {
unsafe {
if let Some(r) = word_ne(a, b, 0) {
eq_op_n(1, 1);
return Some(r);
}
if let Some(r) = word_ne(a, b, 8) {
eq_op_n(1, 2);
return Some(r);
}
if let Some(r) = word_ne(a, b, 16) {
eq_op_n(1, 3);
return Some(r);
}
if let Some(r) = word_ne(a, b, 24) {
eq_op_n(1, 4);
return Some(r);
}
eq_op_n(1, 4);
None
}
}
#[inline(always)]
unsafe fn finish_words(a: *const u8, b: *const u8, mut n: usize, max: usize) -> usize {
debug_assert!(max >= 8 && n % 8 == 0 && n <= max);
unsafe {
let start = n;
let cap = max & !7;
while n < cap {
if let Some(r) = word_ne(a, b, n) {
eq_op_n(1, (n - start) / 8 + 1);
return r;
}
n += 8;
}
eq_op_n(1, (n - start) / 8);
if n == max {
return max;
}
let av = core::ptr::read_unaligned(a.add(max - 8) as *const u64);
let bv = core::ptr::read_unaligned(b.add(max - 8) as *const u64);
let x = av ^ bv;
if x != 0 {
return (max - 8) + (x.trailing_zeros() as usize) / 8;
}
max
}
}
#[inline(always)]
pub(crate) fn count_eq_len_ge8(a: &[u8], b: &[u8], max: usize) -> usize {
debug_assert!(max >= 8 && a.len() >= max && b.len() >= max);
unsafe { count_eq_len_ge8_raw(a.as_ptr(), b.as_ptr(), max) }
}
#[inline(always)]
pub(crate) unsafe fn count_eq_len_ge8_raw(a: *const u8, b: *const u8, max: usize) -> usize {
debug_assert!(max >= 8);
eq_call(max >= 64);
let arm = eqlen_arm();
if arm == 1 {
return unsafe { count_eq_len_words_raw(a, b, 0, max) };
}
if max >= 64 {
if let Some(r) = unsafe { ladder32(a, b) } {
return r;
}
unsafe {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
{
match AVX2_CACHE.load(core::sync::atomic::Ordering::Relaxed) {
1 => return count_eq_len_avx2(a, b, max),
2 => return count_eq_len_words_raw(a, b, 32, max),
_ => return avx2_first_call(a, b, max),
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx2"))]
{
return count_eq_len_avx2(a, b, max);
}
#[cfg(target_arch = "aarch64")]
{
return count_eq_len_neon(a, b, max);
}
#[allow(unreachable_code)]
{
return count_eq_len_words_raw(a, b, 32, max);
}
}
}
unsafe { count_eq_len_small(a, b, max) }
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[cold]
#[inline(never)]
unsafe fn avx2_first_call(a: *const u8, b: *const u8, max: usize) -> usize {
unsafe {
if avx2_detect() {
count_eq_len_avx2(a, b, max)
} else {
count_eq_len_words_raw(a, b, 32, max)
}
}
}
#[cold]
#[inline(never)]
unsafe fn count_eq_len_small(a: *const u8, b: *const u8, max: usize) -> usize {
unsafe { count_eq_len_words_raw(a, b, 0, max) }
}
#[cfg(feature = "profile")]
#[inline]
pub(crate) fn note_eqlen(n: usize) {
const BUCKET: [u8; 65] = {
let mut t = [4u8; 65];
let mut i = 0;
while i <= 64 {
t[i] = if i <= 3 {
0
} else if i <= 5 {
1
} else if i == 6 {
2
} else if i <= 8 {
3
} else {
4
};
i += 1;
}
t
};
let bits = (usize::BITS - n.leading_zeros()) as usize;
let b = BUCKET[bits] as usize;
let _ = counters::TLS.try_with(|t| {
let c = &t.hist[b];
c.set(c.get() + 1);
});
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
static AVX2_CACHE: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[cold]
#[inline(never)]
fn avx2_detect() -> bool {
let yes = is_x86_feature_detected!("avx2");
AVX2_CACHE.store(
if yes { 1 } else { 2 },
core::sync::atomic::Ordering::Relaxed,
);
yes
}
#[cfg(all(target_arch = "x86_64", feature = "std"))]
#[inline(always)]
#[allow(dead_code)] pub(crate) fn avx2_state() -> u8 {
AVX2_CACHE.load(core::sync::atomic::Ordering::Relaxed)
}
#[inline(always)]
pub(crate) fn has_avx2() -> bool {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
{
match AVX2_CACHE.load(core::sync::atomic::Ordering::Relaxed) {
1 => true,
2 => false,
_ => avx2_detect(),
}
}
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
{
cfg!(all(target_arch = "x86_64", target_feature = "avx2"))
}
}
#[inline(always)]
pub(crate) fn has_bmi2() -> bool {
#[cfg(all(target_arch = "x86_64", feature = "std"))]
{
use core::sync::atomic::{AtomicU8, Ordering};
static C: AtomicU8 = AtomicU8::new(0);
#[cold]
#[inline(never)]
fn detect(c: &AtomicU8) -> bool {
let yes = is_x86_feature_detected!("bmi2");
c.store(if yes { 1 } else { 2 }, Ordering::Relaxed);
yes
}
match C.load(Ordering::Relaxed) {
1 => true,
2 => false,
_ => detect(&C),
}
}
#[cfg(not(all(target_arch = "x86_64", feature = "std")))]
{
false
}
}
#[cfg(test)]
#[inline(always)]
pub(crate) fn look_n_bits(container: u64, consumed: u32, n: u32) -> u32 {
debug_assert!((1..=56).contains(&n));
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if has_bmi2() {
return unsafe { look_n_bits_bmi2(container, consumed, n) };
}
look_n_bits_shift(container, consumed, n)
}
#[inline(always)]
#[cfg(test)]
pub(crate) fn look_n_bits_shift(container: u64, consumed: u32, n: u32) -> u32 {
debug_assert!((1..=56).contains(&n));
((container << (consumed & 63)) >> (64 - n)) as u32
}
#[cfg(all(test, target_arch = "x86_64"))]
#[target_feature(enable = "bmi2")]
pub(crate) fn look_n_bits_bmi2(container: u64, consumed: u32, n: u32) -> u32 {
if consumed.saturating_add(n) > 64 {
return look_n_bits_shift(container, consumed, n);
}
unsafe { core::arch::x86_64::_bextr_u64(container, 64 - consumed - n, n) as u32 }
}
#[cfg(any(test, feature = "profile"))]
#[inline]
pub(crate) fn count_eq_len_words(a: &[u8], b: &[u8], max: usize) -> usize {
let max = max.min(a.len()).min(b.len());
if max < 8 {
let mut n = 0usize;
while n < max && a[n] == b[n] {
n += 1;
}
eq_op_n(2, n);
return n;
}
unsafe { count_eq_len_words_raw(a.as_ptr(), b.as_ptr(), 0, max) }
}
#[inline(never)]
pub(crate) unsafe fn count_eq_len_words_raw(
a: *const u8,
b: *const u8,
start: usize,
max: usize,
) -> usize {
debug_assert!(start % 8 == 0 && start <= max && max >= 8);
let mut n = start;
unsafe {
while n + 32 <= max {
eq_op_n(1, 4);
let a0 = core::ptr::read_unaligned(a.add(n) as *const u64);
let b0 = core::ptr::read_unaligned(b.add(n) as *const u64);
if a0 != b0 {
return n + ((a0 ^ b0).trailing_zeros() as usize / 8);
}
let a1 = core::ptr::read_unaligned(a.add(n + 8) as *const u64);
let b1 = core::ptr::read_unaligned(b.add(n + 8) as *const u64);
if a1 != b1 {
return n + 8 + ((a1 ^ b1).trailing_zeros() as usize / 8);
}
let a2 = core::ptr::read_unaligned(a.add(n + 16) as *const u64);
let b2 = core::ptr::read_unaligned(b.add(n + 16) as *const u64);
if a2 != b2 {
return n + 16 + ((a2 ^ b2).trailing_zeros() as usize / 8);
}
let a3 = core::ptr::read_unaligned(a.add(n + 24) as *const u64);
let b3 = core::ptr::read_unaligned(b.add(n + 24) as *const u64);
if a3 != b3 {
return n + 24 + ((a3 ^ b3).trailing_zeros() as usize / 8);
}
n += 32;
}
finish_words(a, b, n, max)
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn count_eq_len_avx2(a: *const u8, b: *const u8, max: usize) -> usize {
use core::arch::x86_64::{
__m256i, _mm256_and_si256, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8,
};
debug_assert!(max >= 64);
let mut n = 32usize;
let mut wide = 0usize;
unsafe {
while n + 64 <= max {
wide += 2;
let e0 = _mm256_cmpeq_epi8(
_mm256_loadu_si256(a.add(n) as *const __m256i),
_mm256_loadu_si256(b.add(n) as *const __m256i),
);
let e1 = _mm256_cmpeq_epi8(
_mm256_loadu_si256(a.add(n + 32) as *const __m256i),
_mm256_loadu_si256(b.add(n + 32) as *const __m256i),
);
if _mm256_movemask_epi8(_mm256_and_si256(e0, e1)) as u32 != 0xFFFF_FFFF {
eq_op_n(0, wide);
let m0 = _mm256_movemask_epi8(e0) as u32;
if m0 != 0xFFFF_FFFF {
return n + m0.trailing_ones() as usize;
}
let m1 = _mm256_movemask_epi8(e1) as u32;
return n + 32 + m1.trailing_ones() as usize;
}
n += 64;
}
if n + 32 <= max {
wide += 1;
let av = _mm256_loadu_si256(a.add(n) as *const __m256i);
let bv = _mm256_loadu_si256(b.add(n) as *const __m256i);
let mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(av, bv)) as u32;
if mask != 0xFFFF_FFFF {
eq_op_n(0, wide);
return n + mask.trailing_ones() as usize;
}
n += 32;
}
if n < max {
wide += 1;
let s = max - 32;
let mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(
_mm256_loadu_si256(a.add(s) as *const __m256i),
_mm256_loadu_si256(b.add(s) as *const __m256i),
)) as u32;
if mask != 0xFFFF_FFFF {
eq_op_n(0, wide);
return s + mask.trailing_ones() as usize;
}
}
eq_op_n(0, wide);
}
max
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn neon_nib_mask(eq: core::arch::aarch64::uint8x16_t) -> u64 {
use core::arch::aarch64::{
vget_lane_u64, vreinterpret_u64_u8, vreinterpretq_u16_u8, vshrn_n_u16,
};
unsafe {
vget_lane_u64::<0>(vreinterpret_u64_u8(vshrn_n_u16::<4>(vreinterpretq_u16_u8(
eq,
))))
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn count_eq_len_neon(a: *const u8, b: *const u8, max: usize) -> usize {
use core::arch::aarch64::{vandq_u8, vceqq_u8, vld1q_u8};
debug_assert!(max >= 64);
let mut n = 32usize;
unsafe {
while n + 32 <= max {
let e0 = vceqq_u8(vld1q_u8(a.add(n)), vld1q_u8(b.add(n)));
let e1 = vceqq_u8(vld1q_u8(a.add(n + 16)), vld1q_u8(b.add(n + 16)));
if neon_nib_mask(vandq_u8(e0, e1)) != u64::MAX {
let m0 = neon_nib_mask(e0);
if m0 != u64::MAX {
return n + (m0.trailing_ones() as usize >> 2);
}
return n + 16 + (neon_nib_mask(e1).trailing_ones() as usize >> 2);
}
n += 32;
}
if n + 16 <= max {
let m = neon_nib_mask(vceqq_u8(vld1q_u8(a.add(n)), vld1q_u8(b.add(n))));
if m != u64::MAX {
return n + (m.trailing_ones() as usize >> 2);
}
n += 16;
}
if n < max {
let s = max - 16;
let m = neon_nib_mask(vceqq_u8(vld1q_u8(a.add(s)), vld1q_u8(b.add(s))));
if m != u64::MAX {
return s + (m.trailing_ones() as usize >> 2);
}
}
}
max
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
fn bytes(a: &[u8], b: &[u8]) -> usize {
let max = a.len().min(b.len());
let mut n = 0usize;
while n < max && a[n] == b[n] {
n += 1;
}
n
}
#[test]
fn load_u32_u64_le_matches_from_le_bytes() {
let mut src = vec![0u8; 64];
for (i, b) in src.iter_mut().enumerate() {
*b = (i.wrapping_mul(37) + 11) as u8;
}
for i in 0..=src.len() - 4 {
let want = u32::from_le_bytes(src[i..i + 4].try_into().unwrap());
assert_eq!(load_u32_le(&src, i), want, "u32 i={i}");
}
for i in 0..=src.len() - 8 {
let want = u64::from_le_bytes(src[i..i + 8].try_into().unwrap());
assert_eq!(load_u64_le(&src, i), want, "u64 i={i}");
}
}
#[test]
fn count_eq_len_matches_byte_and_words() {
let mut src = vec![0u8; 4096];
for (i, b) in src.iter_mut().enumerate() {
*b = (i % 251) as u8;
}
let head: Vec<u8> = src[0..200].to_vec();
src[200..400].copy_from_slice(&head);
let mid: Vec<u8> = src[3..20].to_vec();
src[800..817].copy_from_slice(&mid);
for m in [0usize, 1, 3, 7, 8, 15, 200] {
for ip in [200usize, 201, 400, 800, 801, 2000] {
if m >= src.len() || ip >= src.len() {
continue;
}
for n in [0usize, 1, 7, 8, 9, 31, 32, 33, 64, 200, 512] {
let end_a = (m + n).min(src.len());
let end_b = (ip + n).min(src.len());
let a = &src[m..end_a];
let b = &src[ip..end_b];
let max = a.len().min(b.len());
let want = bytes(a, b);
assert_eq!(
count_eq_len_words(a, b, max),
want,
"words m={m} ip={ip} n={n}"
);
assert_eq!(count_eq_len(a, b), want, "dispatch m={m} ip={ip} n={n}");
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if max >= 64 && is_x86_feature_detected!("avx2") {
let got = unsafe { avx2_with_ladder(a.as_ptr(), b.as_ptr(), max) };
assert_eq!(got, want, "avx2 m={m} ip={ip} n={n}");
}
}
}
}
let long_a = vec![0xA5u8; 10_000];
let mut long_b = long_a.clone();
long_b[9999] = 0x5A;
assert_eq!(count_eq_len(&long_a, &long_b), 9999);
assert_eq!(count_eq_len(&long_a, &long_a), 10_000);
}
#[test]
fn eqlen_bucket_matches_compare_ladder() {
fn ladder(n: usize) -> usize {
match n {
0..=7 => 0,
8..=31 => 1,
32..=63 => 2,
64..=255 => 3,
_ => 4,
}
}
fn lut(n: usize) -> usize {
let bits = (usize::BITS - n.leading_zeros()) as usize;
(if bits <= 3 {
0
} else if bits <= 5 {
1
} else if bits == 6 {
2
} else if bits <= 8 {
3
} else {
4
}) as usize
}
for n in 0usize..=2048 {
assert_eq!(lut(n), ladder(n), "n={n}");
}
for n in [4096usize, 65_535, 65_536, 1 << 20, usize::MAX] {
assert_eq!(lut(n), ladder(n), "n={n}");
}
}
#[test]
fn eq_oracle_exhaustive() {
let mut a = vec![0u8; 260];
for (i, v) in a.iter_mut().enumerate() {
*v = (i.wrapping_mul(97).wrapping_add(13) % 251) as u8;
}
for max in 8usize..=200 {
for p in 0..=max {
let mut b = a.clone();
if p < max {
b[p] ^= 0xFF;
}
let (sa, sb) = (&a[..max], &b[..max]);
let want = p;
assert_eq!(bytes(sa, sb), want, "oracle max={max} p={p}");
assert_eq!(count_eq_len_ge8(sa, sb, max), want, "ge8 max={max} p={p}");
assert_eq!(
count_eq_len_words(sa, sb, max),
want,
"words max={max} p={p}"
);
assert_eq!(count_eq_len(sa, sb), want, "wrapper max={max} p={p}");
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if max >= 64 && is_x86_feature_detected!("avx2") {
let got = unsafe { avx2_with_ladder(sa.as_ptr(), sb.as_ptr(), max) };
assert_eq!(got, want, "avx2 max={max} p={p}");
}
}
}
}
#[test]
fn look_n_bits_bmi2_matches_shift() {
let containers = [
0u64,
1,
u64::MAX,
0x0123_4567_89AB_CDEF,
0x8000_0000_0000_0001,
0x00FF_00FF_00FF_00FF,
];
for c in containers {
for consumed in 0u32..=56 {
for n in 1u32..=24 {
if n > 56 {
continue;
}
let shift = look_n_bits_shift(c, consumed, n);
let got = look_n_bits(c, consumed, n);
assert_eq!(
got, shift,
"c={c:#x} consumed={consumed} n={n} got={got:#x} shift={shift:#x}"
);
#[cfg(all(target_arch = "x86_64", feature = "std"))]
if is_x86_feature_detected!("bmi2") {
let b = unsafe { look_n_bits_bmi2(c, consumed, n) };
assert_eq!(b, shift, "bmi2 c={c:#x} consumed={consumed} n={n}");
}
}
}
}
}
}