#[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
feature = "unstable",
target_arch = "arm",
target_feature = "v7"
))]
use std::arch::is_arm_feature_detected;
#[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
any(target_arch = "x86", target_arch = "x86_64")
))]
use std::arch::is_x86_feature_detected;
#[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "arm", feature = "unstable", target_feature = "v7")
)
))]
use std::sync::OnceLock;
mod arm_neon;
#[allow(dead_code)]
mod portable_simd;
#[allow(dead_code)]
mod pseudo_simd_32;
#[allow(dead_code)]
mod pseudo_simd_64;
mod x86_avx2;
mod x86_sse2;
mod x86_sse4_1;
mod fuzzer;
pub const BODY_OUTLIER_VALUE: u32 = 6;
static_assertions::const_assert!(BODY_OUTLIER_VALUE >= 0b11);
pub const MAX_DISTANCE_SHORT: u32 = 12 * 4 * BODY_OUTLIER_VALUE;
pub const MAX_DISTANCE_NORMAL: u32 = 32 * 4 * BODY_OUTLIER_VALUE;
pub const MAX_DISTANCE_LONG: u32 = 64 * 4 * BODY_OUTLIER_VALUE;
#[allow(clippy::type_complexity)]
#[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "arm", feature = "unstable", target_feature = "v7")
)
))]
#[cfg_attr(
feature = "unstable",
doc(cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features"
)))
)]
static DISPATCH_DISTANCE_32: OnceLock<&'static (dyn Fn(&[u8; 32], &[u8; 32]) -> u32 + Sync)> =
OnceLock::new();
#[allow(clippy::type_complexity)]
#[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "arm", feature = "unstable", target_feature = "v7")
)
))]
#[cfg_attr(
feature = "unstable",
doc(cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features"
)))
)]
static DISPATCH_DISTANCE_64: OnceLock<&'static (dyn Fn(&[u8; 64], &[u8; 64]) -> u32 + Sync)> =
OnceLock::new();
macro_rules! distance_func_template {
{$($name:ident = ($size:literal, $dispatch:path);)*} => {
$(
#[doc = concat!("Computes the distance between two ", stringify!($size), "-byte TLSH bodies.")]
#[inline]
pub fn $name(body1: &[u8; $size], body2: &[u8; $size]) -> u32 {
cfg_if::cfg_if! {
if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
feature = "detect-features",
any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "arm", feature = "unstable", target_feature = "v7")
)
))] {
$dispatch.get_or_init(|| {
#[cfg(all(target_arch = "arm"))]
{
if is_arm_feature_detected!("neon") {
return &|body1, body2| {
#[allow(unsafe_code)]
unsafe {
arm_neon::$name(body1, body2)
}
};
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if is_x86_feature_detected!("avx2") {
return &|body1, body2| {
#[allow(unsafe_code)]
unsafe {
x86_avx2::$name(body1, body2)
}
};
}
if is_x86_feature_detected!("sse4.1") {
return &|body1, body2| {
#[allow(unsafe_code)]
unsafe {
x86_sse4_1::$name(body1, body2)
}
};
}
if is_x86_feature_detected!("sse2") {
return &|body1, body2| {
#[allow(unsafe_code)]
unsafe {
x86_sse2::$name(body1, body2)
}
};
}
}
if usize::BITS >= 64 {
&pseudo_simd_64::$name
} else {
&pseudo_simd_32::$name
}
})(body1, body2)
}
else if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
target_arch = "aarch64",
target_feature = "neon"
))] {
#[allow(unsafe_code)]
unsafe {
arm_neon::$name(body1, body2)
}
}
else if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
target_arch = "arm",
feature = "unstable",
target_feature = "v7",
target_feature = "neon"
))] {
#[allow(unsafe_code)]
unsafe {
arm_neon::$name(body1, body2)
}
}
else if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "avx2"
))] {
#[allow(unsafe_code)]
unsafe {
x86_avx2::$name(body1, body2)
}
}
else if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse4.1"
))] {
#[allow(unsafe_code)]
unsafe {
x86_sse4_1::$name(body1, body2)
}
}
else if #[cfg(all(
feature = "simd-per-arch",
feature = "opt-simd-body-comparison",
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
))] {
#[allow(unsafe_code)]
unsafe {
x86_sse2::$name(body1, body2)
}
}
else if #[cfg(all(
feature = "simd-portable",
feature = "opt-simd-body-comparison"
))] {
portable_simd::$name(body1, body2)
}
else {
if usize::BITS >= 64 {
pseudo_simd_64::$name(body1, body2)
} else {
pseudo_simd_32::$name(body1, body2)
}
}
}
}
)*
}
}
distance_func_template! {
distance_32 = (32, DISPATCH_DISTANCE_32);
distance_64 = (64, DISPATCH_DISTANCE_64);
}
#[cfg_attr(feature = "unstable", coverage(off))]
pub fn distance_12(body1: &[u8; 12], body2: &[u8; 12]) -> u32 {
if usize::BITS >= 64 {
pseudo_simd_64::distance_12(body1, body2)
} else {
pseudo_simd_32::distance_12(body1, body2)
}
}
#[cfg(any(doc, test))]
#[cfg_attr(feature = "unstable", doc(cfg(all())))]
pub(crate) mod naive {
pub fn distance_dibits(x: u8, y: u8) -> u32 {
assert!(x < 4);
assert!(y < 4);
let diff = u32::abs_diff(x as u32, y as u32);
if diff == 0b11 {
super::BODY_OUTLIER_VALUE
} else {
diff
}
}
pub fn distance<const N: usize>(body1: &[u8; N], body2: &[u8; N]) -> u32 {
body1
.iter()
.zip(body2.iter())
.map(|(&x, &y)| {
(0..4u32)
.map(move |i| {
let nx = (x >> (i * 2)) & 0b11;
let ny = (y >> (i * 2)) & 0b11;
distance_dibits(nx, ny)
})
.sum::<u32>()
})
.sum::<u32>()
}
}
mod tests;