use std::sync::OnceLock;
static LOG2_TABLE: OnceLock<[f64; 256]> = OnceLock::new();
#[inline]
pub(crate) fn get_log2_table() -> &'static [f64; 256] {
LOG2_TABLE.get_or_init(|| {
let mut table = [0.0f64; 256];
for i in 1..256 {
let val = i as f64;
table[i] = val * val.log2();
}
table
})
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum X86EntropyTier {
Avx512,
Avx2,
Scalar,
}
#[cfg(target_arch = "x86_64")]
static X86_ENTROPY_TIER: OnceLock<X86EntropyTier> = OnceLock::new();
#[cfg(target_arch = "x86_64")]
pub(crate) fn resolve_x86_entropy_tier() -> X86EntropyTier {
*X86_ENTROPY_TIER.get_or_init(|| {
let tier = if is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw")
&& is_x86_feature_detected!("avx512dq")
{
X86EntropyTier::Avx512
} else if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
X86EntropyTier::Avx2
} else {
X86EntropyTier::Scalar
};
tracing::info!(
tier = ?tier,
"keyhog entropy: selected x86_64 SIMD tier for the Shannon-entropy reduction"
);
tier
})
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn shannon_entropy_simd(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
match resolve_x86_entropy_tier() {
X86EntropyTier::Avx512 => unsafe {
crate::entropy::avx512::calculate_shannon_entropy(data)
},
X86EntropyTier::Avx2 => unsafe { crate::entropy::fast_x86::shannon_entropy_avx2(data) },
X86EntropyTier::Scalar => shannon_entropy_scalar(data),
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn shannon_entropy_simd(data: &[u8]) -> f64 {
crate::entropy::fast_neon::shannon_entropy_neon(data)
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub(crate) fn shannon_entropy_simd(data: &[u8]) -> f64 {
shannon_entropy_scalar(data)
}
#[inline]
pub(crate) fn histogram_8way(data: &[u8]) -> ([u32; 256], usize) {
let mut c0 = [0u32; 256];
let mut c1 = [0u32; 256];
let mut c2 = [0u32; 256];
let mut c3 = [0u32; 256];
let mut c4 = [0u32; 256];
let mut c5 = [0u32; 256];
let mut c6 = [0u32; 256];
let mut c7 = [0u32; 256];
let mut active_len = data.len();
let mut chunks = data.chunks_exact(8);
for chunk in &mut chunks {
if chunk[0] == 0
&& chunk[1] == 0
&& chunk[2] == 0
&& chunk[3] == 0
&& chunk[4] == 0
&& chunk[5] == 0
&& chunk[6] == 0
&& chunk[7] == 0
{
active_len -= 8;
continue;
}
c0[chunk[0] as usize] += 1;
c1[chunk[1] as usize] += 1;
c2[chunk[2] as usize] += 1;
c3[chunk[3] as usize] += 1;
c4[chunk[4] as usize] += 1;
c5[chunk[5] as usize] += 1;
c6[chunk[6] as usize] += 1;
c7[chunk[7] as usize] += 1;
}
for &byte in chunks.remainder() {
if byte == 0 {
active_len -= 1;
} else {
c0[byte as usize] += 1;
}
}
let mut counts = [0u32; 256];
for j in 0..256 {
counts[j] = c0[j] + c1[j] + c2[j] + c3[j] + c4[j] + c5[j] + c6[j] + c7[j];
}
(counts, active_len)
}
#[inline]
pub(crate) fn entropy_from_histogram(counts: &[u32; 256], active_len: usize) -> f64 {
if active_len == 0 {
return 0.0;
}
if active_len <= 255 {
let table = get_log2_table();
let mut sum = 0.0;
for &count in counts {
if count > 0 {
sum += table[count as usize];
}
}
return (active_len as f64).log2() - sum / (active_len as f64);
}
let len_f = active_len as f64;
let mut entropy = 0.0;
for &count in counts {
if count > 0 {
let p = count as f64 / len_f;
entropy -= p * p.log2();
}
}
entropy
}
#[inline]
pub(crate) fn shannon_entropy_scalar(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let (counts, active_len) = histogram_8way(data);
entropy_from_histogram(&counts, active_len)
}