#[cfg(feature = "std")]
use std::sync::OnceLock;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CpuLevel {
Scalar,
Sse2,
Sse42,
Bmi2,
Avx2,
Avx512,
Neon,
Sve,
}
impl CpuLevel {
pub const ALL: [CpuLevel; 8] = [
CpuLevel::Scalar,
CpuLevel::Sse2,
CpuLevel::Sse42,
CpuLevel::Bmi2,
CpuLevel::Avx2,
CpuLevel::Avx512,
CpuLevel::Neon,
CpuLevel::Sve,
];
pub const fn name(self) -> &'static str {
match self {
CpuLevel::Scalar => "scalar",
CpuLevel::Sse2 => "sse2",
CpuLevel::Sse42 => "sse4.2",
CpuLevel::Bmi2 => "bmi2",
CpuLevel::Avx2 => "avx2",
CpuLevel::Avx512 => "avx512",
CpuLevel::Neon => "neon",
CpuLevel::Sve => "sve",
}
}
const fn ladder(self) -> (Ladder, u8) {
match self {
CpuLevel::Scalar => (Ladder::Any, 0),
CpuLevel::Sse2 => (Ladder::X86, 1),
CpuLevel::Sse42 => (Ladder::X86, 2),
CpuLevel::Bmi2 => (Ladder::X86, 3),
CpuLevel::Avx2 => (Ladder::X86, 4),
CpuLevel::Avx512 => (Ladder::X86, 5),
CpuLevel::Neon => (Ladder::Arm, 1),
CpuLevel::Sve => (Ladder::Arm, 2),
}
}
const fn native(self) -> bool {
match self.ladder().0 {
Ladder::Any => true,
Ladder::X86 => cfg!(any(target_arch = "x86", target_arch = "x86_64")),
Ladder::Arm => cfg!(target_arch = "aarch64"),
}
}
}
impl core::fmt::Display for CpuLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct UnknownCpuLevel;
impl core::fmt::Display for UnknownCpuLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("unknown CPU level; expected one of ")?;
for (index, level) in CpuLevel::ALL.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
f.write_str(level.name())?;
}
Ok(())
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnknownCpuLevel {}
impl core::str::FromStr for CpuLevel {
type Err = UnknownCpuLevel;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let lower = |expected: &str| text.eq_ignore_ascii_case(expected);
CpuLevel::ALL
.into_iter()
.find(|level| lower(level.name()))
.or_else(|| {
if lower("sse42") {
Some(CpuLevel::Sse42)
} else if lower("avx-512") || lower("avx512f") {
Some(CpuLevel::Avx512)
} else {
None
}
})
.ok_or(UnknownCpuLevel)
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum Ladder {
Any,
X86,
Arm,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CpuCeilingError {
AlreadyResolved,
OtherArchitecture(CpuLevel),
Unsupported,
}
impl core::fmt::Display for CpuCeilingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CpuCeilingError::AlreadyResolved => {
f.write_str("the CPU kernels were already chosen; set the ceiling before any work")
}
CpuCeilingError::OtherArchitecture(level) => {
write!(f, "{level} is not an instruction set of this architecture")
}
CpuCeilingError::Unsupported => f.write_str("this target cannot hold a CPU ceiling"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CpuCeilingError {}
#[cfg(target_has_atomic = "8")]
static CEILING: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
#[cfg(target_has_atomic = "8")]
const CEILING_NONE: u8 = u8::MAX;
pub fn set_cpu_ceiling(level: CpuLevel) -> Result<(), CpuCeilingError> {
if !level.native() {
return Err(CpuCeilingError::OtherArchitecture(level));
}
#[cfg(target_has_atomic = "8")]
{
use core::sync::atomic::Ordering;
let index = CpuLevel::ALL
.iter()
.position(|candidate| *candidate == level)
.expect("every level is in ALL") as u8;
CEILING
.compare_exchange(0, index + 1, Ordering::AcqRel, Ordering::Acquire)
.map(|_| ())
.map_err(|_| CpuCeilingError::AlreadyResolved)
}
#[cfg(not(target_has_atomic = "8"))]
{
Err(CpuCeilingError::Unsupported)
}
}
pub fn cpu_ceiling() -> Option<CpuLevel> {
#[cfg(target_has_atomic = "8")]
{
use core::sync::atomic::Ordering;
let mut stored = CEILING.load(Ordering::Acquire);
if stored == 0 {
stored = match CEILING.compare_exchange(
0,
CEILING_NONE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => CEILING_NONE,
Err(stored) => stored,
};
}
(stored != CEILING_NONE).then(|| CpuLevel::ALL[usize::from(stored - 1)])
}
#[cfg(not(target_has_atomic = "8"))]
{
None
}
}
#[cfg_attr(
not(any(
test,
target_arch = "x86_64",
all(target_arch = "x86", feature = "kernel-sse"),
all(target_arch = "aarch64", feature = "kernel-neon"),
)),
allow(dead_code)
)]
const fn allowed_under(ceiling: Option<CpuLevel>, needed: CpuLevel) -> bool {
let Some(ceiling) = ceiling else {
return true;
};
let (needed_ladder, needed_rung) = needed.ladder();
let (ceiling_ladder, ceiling_rung) = ceiling.ladder();
needed_rung == 0 || (needed_ladder as u8 == ceiling_ladder as u8 && needed_rung <= ceiling_rung)
}
#[cfg_attr(
not(any(
target_arch = "x86_64",
all(target_arch = "x86", feature = "kernel-sse"),
all(target_arch = "aarch64", feature = "kernel-neon"),
)),
allow(dead_code)
)]
#[inline]
pub(crate) fn cpu_allows(needed: CpuLevel) -> bool {
allowed_under(cpu_ceiling(), needed)
}
pub trait CpuKernel: Copy + 'static {
fn mask_lower_bits(value: u64, n: u8) -> u64;
#[inline(always)]
fn extract_triple(packed: u64, n1: u8, n2: u8, n3: u8) -> (u64, u64, u64) {
(
Self::mask_lower_bits(packed.wrapping_shr(u32::from(n3) + u32::from(n2)), n1),
Self::mask_lower_bits(packed.wrapping_shr(u32::from(n3)), n2),
Self::mask_lower_bits(packed, n3),
)
}
const COPY_CHUNK: usize = crate::decoding::simd_copy::PORTABLE_COPY_CHUNK;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_chunks_portable(src, dst, len) }
}
const STEP_CHUNK: usize = Self::COPY_CHUNK;
#[inline(always)]
unsafe fn copy_step(src: *const u8, dst: *mut u8, len: usize) {
unsafe { Self::copy_chunks(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { crate::decoding::simd_copy::copy16_portable(src, dst) }
}
}
#[derive(Copy, Clone, Default)]
pub(crate) struct BaselineKernel;
impl CpuKernel for BaselineKernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
ScalarKernel::mask_lower_bits(value, n)
}
const COPY_CHUNK: usize = crate::decoding::simd_copy::BASELINE_COPY_CHUNK;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_chunks_baseline(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { crate::decoding::simd_copy::copy16_baseline(src, dst) }
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
macro_rules! sse2_copies {
() => {
const COPY_CHUNK: usize = 16;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, 16) }
}
};
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
#[derive(Copy, Clone, Default)]
pub(crate) struct Sse2Kernel;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
impl CpuKernel for Sse2Kernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
ScalarKernel::mask_lower_bits(value, n)
}
sse2_copies!();
}
#[derive(Copy, Clone, Default)]
pub struct ScalarKernel;
pub(crate) const BIT_MASK: [u64; 256] = {
let mut table = [u64::MAX; 256];
let mut i: usize = 0;
while i < 64 {
table[i] = (1u64 << i) - 1;
i += 1;
}
table
};
impl CpuKernel for ScalarKernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
value & BIT_MASK[n as usize]
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-bmi2"
))]
#[derive(Copy, Clone, Default)]
pub(crate) struct Bmi2Kernel;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-bmi2"
))]
impl CpuKernel for Bmi2Kernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
unsafe { mask_lower_bits_bmi2_impl(value, n) }
}
sse2_copies!();
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
#[derive(Copy, Clone, Default)]
pub(crate) struct Avx2Kernel;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
impl CpuKernel for Avx2Kernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
#[cfg(target_arch = "x86_64")]
unsafe {
mask_lower_bits_bmi2_impl(value, n)
}
#[cfg(target_arch = "x86")]
{
ScalarKernel::mask_lower_bits(value, n)
}
}
const COPY_CHUNK: usize = 32;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_avx2(src, dst, len) }
}
const STEP_CHUNK: usize = 16;
#[inline(always)]
unsafe fn copy_step(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, 16) }
}
}
#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
#[derive(Copy, Clone, Default)]
pub(crate) struct Vbmi2Kernel;
#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
impl CpuKernel for Vbmi2Kernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
unsafe { mask_lower_bits_bmi2_impl(value, n) }
}
const COPY_CHUNK: usize = 32;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_avx2(src, dst, len) }
}
const STEP_CHUNK: usize = 16;
#[inline(always)]
unsafe fn copy_step(src: *const u8, dst: *mut u8, len: usize) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { crate::decoding::simd_copy::copy_sse2(src, dst, 16) }
}
}
#[cfg(all(target_arch = "aarch64", feature = "kernel-neon"))]
#[derive(Copy, Clone, Default)]
pub(crate) struct NeonKernel;
#[cfg(all(target_arch = "aarch64", feature = "kernel-neon"))]
impl CpuKernel for NeonKernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
ScalarKernel::mask_lower_bits(value, n)
}
const COPY_CHUNK: usize = BaselineKernel::COPY_CHUNK;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { BaselineKernel::copy_chunks(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { BaselineKernel::copy16(src, dst) }
}
}
#[cfg(all(target_arch = "aarch64", feature = "kernel-sve"))]
#[allow(dead_code)]
#[derive(Copy, Clone, Default)]
pub(crate) struct SveKernel;
#[cfg(all(target_arch = "aarch64", feature = "kernel-sve"))]
impl CpuKernel for SveKernel {
#[inline(always)]
fn mask_lower_bits(value: u64, n: u8) -> u64 {
ScalarKernel::mask_lower_bits(value, n)
}
const COPY_CHUNK: usize = BaselineKernel::COPY_CHUNK;
#[inline(always)]
unsafe fn copy_chunks(src: *const u8, dst: *mut u8, len: usize) {
unsafe { BaselineKernel::copy_chunks(src, dst, len) }
}
#[inline(always)]
unsafe fn copy16(src: *const u8, dst: *mut u8) {
unsafe { BaselineKernel::copy16(src, dst) }
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-bmi2"
))]
#[target_feature(enable = "bmi2")]
#[inline]
unsafe fn mask_lower_bits_bmi2_impl(value: u64, n: u8) -> u64 {
#[cfg(target_arch = "x86_64")]
{
core::arch::x86_64::_bzhi_u64(value, n as u32)
}
#[cfg(target_arch = "x86")]
{
use core::arch::x86::_bzhi_u32;
if n >= 64 {
return value;
}
if n <= 32 {
return u64::from(_bzhi_u32(value as u32, u32::from(n)));
}
let high = _bzhi_u32((value >> 32) as u32, u32::from(n) - 32);
(value & u64::from(u32::MAX)) | (u64::from(high) << 32)
}
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
#[allow(unused_variables)]
const fn select_x86_kernel(
has_avx512vbmi2: bool,
has_avx512f: bool,
has_avx512vl: bool,
has_avx512bw: bool,
has_bmi2: bool,
has_avx2: bool,
has_sse2: bool,
) -> CpuKernelTag {
#[cfg(feature = "kernel-vbmi2")]
if has_avx512vbmi2 && has_avx512f && has_avx512vl && has_avx512bw && has_bmi2 && has_avx2 {
return CpuKernelTag::Vbmi2;
}
#[cfg(feature = "kernel-avx2")]
if has_avx2 && has_bmi2 {
return CpuKernelTag::Avx2;
}
#[cfg(feature = "kernel-bmi2")]
if has_bmi2 {
return CpuKernelTag::Bmi2;
}
#[cfg(feature = "kernel-sse")]
if has_sse2 {
return CpuKernelTag::Sse2;
}
CpuKernelTag::Scalar
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum CpuKernelTag {
Scalar,
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
Sse2,
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-bmi2"
))]
Bmi2,
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
Avx2,
#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
Vbmi2,
#[cfg(all(target_arch = "aarch64", feature = "kernel-neon"))]
Neon,
#[cfg(all(
target_arch = "aarch64",
feature = "kernel-sve",
any(feature = "std", target_feature = "sve"),
))]
Sve,
}
#[cfg(feature = "std")]
pub(crate) fn detect_cpu_kernel() -> CpuKernelTag {
static CACHED: OnceLock<CpuKernelTag> = OnceLock::new();
*CACHED.get_or_init(detect_cpu_kernel_uncached)
}
#[cfg(feature = "std")]
fn detect_cpu_kernel_uncached() -> CpuKernelTag {
#[cfg(target_arch = "x86_64")]
{
use std::arch::is_x86_feature_detected;
let avx512 = cfg!(feature = "kernel-vbmi2") && cpu_allows(CpuLevel::Avx512);
return select_x86_kernel(
avx512 && is_x86_feature_detected!("avx512vbmi2"),
avx512 && is_x86_feature_detected!("avx512f"),
avx512 && is_x86_feature_detected!("avx512vl"),
avx512 && is_x86_feature_detected!("avx512bw"),
cfg!(feature = "kernel-bmi2")
&& cpu_allows(CpuLevel::Bmi2)
&& is_x86_feature_detected!("bmi2"),
cfg!(feature = "kernel-avx2")
&& cpu_allows(CpuLevel::Avx2)
&& is_x86_feature_detected!("avx2"),
cfg!(feature = "kernel-sse")
&& cpu_allows(CpuLevel::Sse2)
&& is_x86_feature_detected!("sse2"),
);
}
#[cfg(target_arch = "x86")]
{
#[cfg(feature = "kernel-sse")]
{
use std::arch::is_x86_feature_detected;
#[cfg(feature = "kernel-bmi2")]
{
let bmi2 = cpu_allows(CpuLevel::Bmi2) && is_x86_feature_detected!("bmi2");
#[cfg(feature = "kernel-avx2")]
if bmi2 && cpu_allows(CpuLevel::Avx2) && is_x86_feature_detected!("avx2") {
return CpuKernelTag::Avx2;
}
if bmi2 {
return CpuKernelTag::Bmi2;
}
}
if cpu_allows(CpuLevel::Sse2) && is_x86_feature_detected!("sse2") {
return CpuKernelTag::Sse2;
}
}
return CpuKernelTag::Scalar;
}
#[cfg(target_arch = "aarch64")]
{
#[cfg(any(feature = "kernel-sve", feature = "kernel-neon"))]
use std::arch::is_aarch64_feature_detected;
#[cfg(feature = "kernel-sve")]
if cpu_allows(CpuLevel::Sve) && is_aarch64_feature_detected!("sve") {
return CpuKernelTag::Sve;
}
#[cfg(feature = "kernel-neon")]
if cpu_allows(CpuLevel::Neon) && is_aarch64_feature_detected!("neon") {
return CpuKernelTag::Neon;
}
return CpuKernelTag::Scalar;
}
#[allow(unreachable_code)]
CpuKernelTag::Scalar
}
#[cfg(not(feature = "std"))]
pub(crate) fn detect_cpu_kernel() -> CpuKernelTag {
#[cfg(target_arch = "x86_64")]
{
let avx512 = cpu_allows(CpuLevel::Avx512);
return select_x86_kernel(
avx512 && cfg!(target_feature = "avx512vbmi2"),
avx512 && cfg!(target_feature = "avx512f"),
avx512 && cfg!(target_feature = "avx512vl"),
avx512 && cfg!(target_feature = "avx512bw"),
cpu_allows(CpuLevel::Bmi2) && cfg!(target_feature = "bmi2"),
cpu_allows(CpuLevel::Avx2) && cfg!(target_feature = "avx2"),
cpu_allows(CpuLevel::Sse2) && cfg!(target_feature = "sse2"),
);
}
#[cfg(all(target_arch = "x86", feature = "kernel-bmi2"))]
{
let bmi2 = cpu_allows(CpuLevel::Bmi2) && cfg!(target_feature = "bmi2");
#[cfg(feature = "kernel-avx2")]
if bmi2 && cpu_allows(CpuLevel::Avx2) && cfg!(target_feature = "avx2") {
return CpuKernelTag::Avx2;
}
if bmi2 {
return CpuKernelTag::Bmi2;
}
}
#[cfg(all(target_arch = "x86", feature = "kernel-sse"))]
if cpu_allows(CpuLevel::Sse2) && cfg!(target_feature = "sse2") {
return CpuKernelTag::Sse2;
}
#[cfg(target_arch = "aarch64")]
{
#[cfg(all(feature = "kernel-sve", target_feature = "sve"))]
if cpu_allows(CpuLevel::Sve) {
return CpuKernelTag::Sve;
}
#[cfg(all(feature = "kernel-neon", target_feature = "neon"))]
if cpu_allows(CpuLevel::Neon) {
return CpuKernelTag::Neon;
}
}
#[allow(unreachable_code)]
CpuKernelTag::Scalar
}
impl CpuKernelTag {
pub(crate) fn name(self) -> &'static str {
match self {
CpuKernelTag::Scalar => "scalar",
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-sse"
))]
CpuKernelTag::Sse2 => "sse2",
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-bmi2"
))]
CpuKernelTag::Bmi2 => "bmi2",
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "kernel-avx2"
))]
CpuKernelTag::Avx2 => "avx2",
#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
CpuKernelTag::Vbmi2 => "vbmi2",
#[cfg(all(target_arch = "aarch64", feature = "kernel-neon"))]
CpuKernelTag::Neon => "neon",
#[cfg(all(
target_arch = "aarch64",
feature = "kernel-sve",
any(feature = "std", target_feature = "sve"),
))]
CpuKernelTag::Sve => "sve",
}
}
}
pub fn active_cpu_kernel_name() -> &'static str {
detect_cpu_kernel().name()
}
#[cfg(test)]
mod tests;