#![allow(clippy::inline_always)]
use core::fmt::Debug;
use crate::word::{
compress_broadword, expand_broadword, select_broadword64, xor_smear, xor_smear_down,
};
mod sealed {
pub trait Sealed {}
}
pub trait Isa:
Copy + Eq + core::hash::Hash + Send + Sync + Debug + 'static + sealed::Sealed
{
type U8x16: crate::lanes::Lanes<Isa = Self, Bitmask = u16>;
fn run<R>(self, f: impl FnOnce(Self) -> R) -> R;
#[doc(hidden)]
fn pext_u32(self, x: u32, mask: u32) -> u32;
#[doc(hidden)]
fn pext_u64(self, x: u64, mask: u64) -> u64;
#[doc(hidden)]
fn pdep_u32(self, x: u32, mask: u32) -> u32;
#[doc(hidden)]
fn pdep_u64(self, x: u64, mask: u64) -> u64;
#[doc(hidden)]
#[inline(always)]
fn unzip_u32(self, x: u32) -> (u32, u32) {
crate::word::unzip_broadword(x)
}
#[doc(hidden)]
#[inline(always)]
fn unzip_u64(self, x: u64) -> (u64, u64) {
(
crate::word::gather_even_u64(x),
crate::word::gather_even_u64(x >> 1),
)
}
#[doc(hidden)]
fn select_u64(self, x: u64, k: u32) -> u32;
#[doc(hidden)]
fn xor_scan_u64(self, x: u64) -> u64;
#[doc(hidden)]
fn xor_scan_down_u64(self, x: u64) -> u64;
#[inline(always)]
fn pext<W: crate::Word>(self, x: W, mask: W) -> W {
x.pext_in(mask, self)
}
#[inline(always)]
fn pdep<W: crate::Word>(self, x: W, mask: W) -> W {
x.pdep_in(mask, self)
}
#[inline(always)]
fn unzip<W: crate::Word>(self, x: W) -> (W, W) {
x.unzip_in(self)
}
#[inline(always)]
fn zip<W: crate::Word>(self, a: W, b: W) -> W {
a.zip_in(b, self)
}
#[inline(always)]
fn select<W: crate::Word>(self, x: W, k: u32) -> Option<u32> {
(k < x.count_ones()).then(|| x.select_lowest_in(k, self))
}
#[inline(always)]
fn xor_scan<W: crate::Word>(self, x: W) -> W {
x.xor_scan_in(self)
}
#[inline(always)]
fn xor_scan_down<W: crate::Word>(self, x: W) -> W {
x.xor_scan_down_in(self)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Portable;
impl sealed::Sealed for Portable {}
impl Isa for Portable {
type U8x16 = crate::lanes::Swar16<Self>;
#[inline(always)]
fn run<R>(self, f: impl FnOnce(Self) -> R) -> R {
f(self)
}
#[inline(always)]
fn pext_u32(self, x: u32, mask: u32) -> u32 {
compress_broadword(x, mask)
}
#[inline(always)]
fn pext_u64(self, x: u64, mask: u64) -> u64 {
compress_broadword(x, mask)
}
#[inline(always)]
fn pdep_u32(self, x: u32, mask: u32) -> u32 {
expand_broadword(x, mask)
}
#[inline(always)]
fn pdep_u64(self, x: u64, mask: u64) -> u64 {
expand_broadword(x, mask)
}
#[inline(always)]
fn select_u64(self, x: u64, k: u32) -> u32 {
select_broadword64(x, k)
}
#[inline(always)]
fn xor_scan_u64(self, x: u64) -> u64 {
xor_smear(x)
}
#[inline(always)]
fn xor_scan_down_u64(self, x: u64) -> u64 {
xor_smear_down(x)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Native;
impl sealed::Sealed for Native {}
macro_rules! native_bmi2 {
($hw:expr, $sw:expr) => {{
#[cfg(all(
target_arch = "x86_64",
target_feature = "bmi2",
not(feature = "portable")
))]
{
unsafe { $hw }
}
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "bmi2",
not(feature = "portable")
)))]
{
$sw
}
}};
}
macro_rules! native_clmul {
($hw:expr, $sw:expr) => {{
#[cfg(all(
target_arch = "x86_64",
target_feature = "pclmulqdq",
not(feature = "portable")
))]
{
unsafe { $hw }
}
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "pclmulqdq",
not(feature = "portable")
)))]
{
$sw
}
}};
}
#[allow(unsafe_code)]
impl Isa for Native {
type U8x16 = crate::lanes::NativeU8x16;
#[inline(always)]
fn run<R>(self, f: impl FnOnce(Self) -> R) -> R {
f(self)
}
#[inline(always)]
fn pext_u32(self, x: u32, mask: u32) -> u32 {
native_bmi2!(
crate::x86::bmi2::pext_u32(x, mask),
compress_broadword(x, mask)
)
}
#[inline(always)]
fn pext_u64(self, x: u64, mask: u64) -> u64 {
native_bmi2!(
crate::x86::bmi2::pext_u64(x, mask),
compress_broadword(x, mask)
)
}
#[inline(always)]
fn pdep_u32(self, x: u32, mask: u32) -> u32 {
native_bmi2!(
crate::x86::bmi2::pdep_u32(x, mask),
expand_broadword(x, mask)
)
}
#[inline(always)]
fn unzip_u32(self, x: u32) -> (u32, u32) {
native_bmi2!(
(
crate::x86::bmi2::pext_u32(x, 0x5555_5555),
crate::x86::bmi2::pext_u32(x, 0xAAAA_AAAA)
),
crate::word::unzip_broadword(x)
)
}
#[inline(always)]
fn unzip_u64(self, x: u64) -> (u64, u64) {
native_bmi2!(
(
crate::x86::bmi2::pext_u64(x, 0x5555_5555_5555_5555),
crate::x86::bmi2::pext_u64(x, 0xAAAA_AAAA_AAAA_AAAA)
),
(
crate::word::gather_even_u64(x),
crate::word::gather_even_u64(x >> 1)
)
)
}
#[inline(always)]
fn pdep_u64(self, x: u64, mask: u64) -> u64 {
native_bmi2!(
crate::x86::bmi2::pdep_u64(x, mask),
expand_broadword(x, mask)
)
}
#[inline(always)]
fn select_u64(self, x: u64, k: u32) -> u32 {
native_bmi2!(crate::x86::bmi2::select_u64(x, k), select_broadword64(x, k))
}
#[inline(always)]
fn xor_scan_u64(self, x: u64) -> u64 {
native_clmul!(crate::x86::pclmulqdq::xor_scan_u64(x), xor_smear(x))
}
#[inline(always)]
fn xor_scan_down_u64(self, x: u64) -> u64 {
native_clmul!(
crate::x86::pclmulqdq::xor_scan_down_u64(x),
xor_smear_down(x)
)
}
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
macro_rules! x86_level {
(
$(#[$doc:meta])*
$module:ident, $name:ident, $features:literal, $detect:path, gfni = $gfni:literal, bmi2 = $bmi2:literal, clmul = $clmul:literal
) => {
#[allow(unsafe_code)]
mod $module {
use super::{Isa, X86Level, sealed};
use crate::x86::{bmi2, pclmulqdq};
$(#[$doc])*
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct $name(());
impl core::fmt::Debug for $name {
#[allow(clippy::missing_inline_in_public_items)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(stringify!($name))
}
}
impl $name {
#[inline]
#[must_use]
pub fn detect() -> Option<Self> {
$detect().then_some(Self(()))
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked() -> Self {
Self(())
}
pub const FEATURES: &str = $features;
}
impl sealed::Sealed for $name {}
impl Isa for $name {
type U8x16 = crate::lanes::X86x16<Self>;
#[inline]
fn run<R>(self, f: impl FnOnce(Self) -> R) -> R {
#[target_feature(enable = $features)]
#[inline]
fn trampoline<R>(cpu: $name, f: impl FnOnce($name) -> R) -> R {
f(cpu)
}
unsafe { trampoline(self, f) }
}
#[inline(always)]
fn pext_u32(self, x: u32, mask: u32) -> u32 {
if $bmi2 { unsafe { bmi2::pext_u32(x, mask) } } else { crate::word::compress_broadword(x, mask) }
}
#[inline(always)]
fn pext_u64(self, x: u64, mask: u64) -> u64 {
if $bmi2 { unsafe { bmi2::pext_u64(x, mask) } } else { crate::word::compress_broadword(x, mask) }
}
#[inline(always)]
fn pdep_u32(self, x: u32, mask: u32) -> u32 {
if $bmi2 { unsafe { bmi2::pdep_u32(x, mask) } } else { crate::word::expand_broadword(x, mask) }
}
#[inline(always)]
fn unzip_u32(self, x: u32) -> (u32, u32) {
if $bmi2 { unsafe { (bmi2::pext_u32(x, 0x5555_5555), bmi2::pext_u32(x, 0xAAAA_AAAA)) } } else { crate::word::unzip_broadword(x) }
}
#[inline(always)]
fn unzip_u64(self, x: u64) -> (u64, u64) {
const EVEN: u64 = 0x5555_5555_5555_5555;
if $bmi2 { unsafe { (bmi2::pext_u64(x, EVEN), bmi2::pext_u64(x, !EVEN)) } } else { (crate::word::gather_even_u64(x), crate::word::gather_even_u64(x >> 1)) }
}
#[inline(always)]
fn pdep_u64(self, x: u64, mask: u64) -> u64 {
if $bmi2 { unsafe { bmi2::pdep_u64(x, mask) } } else { crate::word::expand_broadword(x, mask) }
}
#[inline(always)]
fn select_u64(self, x: u64, k: u32) -> u32 {
if $bmi2 { unsafe { bmi2::select_u64(x, k) } } else { crate::word::select_broadword64(x, k) }
}
#[inline(always)]
fn xor_scan_u64(self, x: u64) -> u64 {
if $clmul { unsafe { pclmulqdq::xor_scan_u64(x) } } else { crate::word::xor_smear(x) }
}
#[inline(always)]
fn xor_scan_down_u64(self, x: u64) -> u64 {
if $clmul { unsafe { pclmulqdq::xor_scan_down_u64(x) } } else { crate::word::xor_smear_down(x) }
}
}
impl X86Level for $name {
const GFNI: bool = $gfni;
#[inline]
unsafe fn assume() -> Self {
Self(())
}
}
}
pub use $module::$name;
};
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
x86_level! {
x86v3, X86V3,
"sse3,ssse3,sse4.1,sse4.2,popcnt,cmpxchg16b,avx,avx2,bmi1,bmi2,fma,lzcnt,movbe,f16c,xsave,pclmulqdq",
crate::cpu::x86v3, gfni = false, bmi2 = true, clmul = true
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
x86_level! {
x86v4, X86V4,
"sse3,ssse3,sse4.1,sse4.2,popcnt,cmpxchg16b,avx,avx2,bmi1,bmi2,fma,lzcnt,movbe,f16c,xsave,pclmulqdq,avx512f,avx512dq,avx512cd,avx512bw,avx512vl,avx512vbmi,gfni",
crate::cpu::x86v4, gfni = true, bmi2 = true, clmul = true
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
x86_level! {
x86v2, X86V2,
"sse3,ssse3,sse4.1,sse4.2,popcnt,cmpxchg16b",
crate::cpu::x86v2, gfni = false, bmi2 = false, clmul = false
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Level {
Portable(Portable),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
X86V3(X86V3),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
X86V2(X86V2),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
X86V4(X86V4),
Native(Native),
}
#[inline]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
#[allow(unsafe_code)]
pub fn detect() -> Level {
#[cfg(all(
target_arch = "x86_64",
target_feature = "sse2",
not(feature = "portable")
))]
{
use crate::cpu::{self, x86v2_in_build, x86v3_in_build, x86v4_in_build};
if x86v4_in_build() {
return Level::Native(Native);
}
let has = cpu::levels();
if has & cpu::X86V4 != 0 {
return Level::X86V4(unsafe { X86V4::new_unchecked() });
}
if x86v3_in_build() {
return Level::Native(Native);
}
if has & cpu::X86V3 != 0 {
return Level::X86V3(unsafe { X86V3::new_unchecked() });
}
if x86v2_in_build() {
return Level::Native(Native);
}
if has & cpu::X86V2 != 0 {
return Level::X86V2(unsafe { X86V2::new_unchecked() });
}
}
Level::Portable(Portable)
}
#[allow(clippy::missing_inline_in_public_items)]
pub fn available() -> impl Iterator<Item = Level> {
let top = detect();
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
let (v2, v3, v4) = (
X86V2::detect().map(Level::X86V2),
X86V3::detect().map(Level::X86V3),
X86V4::detect().map(Level::X86V4),
);
#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2")))]
let (v2, v3, v4) = (None, None, None);
[
Some(Level::Portable(Portable)),
v2,
v3,
v4,
matches!(top, Level::Native(_)).then_some(top),
]
.into_iter()
.flatten()
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "sse2",
not(feature = "portable")
))]
#[derive(Clone, Copy)]
pub(crate) struct Batch {
pub avx2: bool,
pub vbmi: bool,
pub vbmi_gfni: bool,
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "sse2",
not(feature = "portable")
))]
#[inline]
pub(crate) fn batch() -> Batch {
let level = detect();
let v4 = matches!(level, Level::X86V4(_));
let v3 = v4 || matches!(level, Level::X86V3(_));
let vbmi = cfg!(all(
target_feature = "avx512f",
target_feature = "avx512bw",
target_feature = "avx512vbmi"
));
Batch {
avx2: v3 || cfg!(target_feature = "avx2"),
vbmi: v4 || vbmi,
vbmi_gfni: v4 || (vbmi && cfg!(target_feature = "gfni")),
}
}
#[macro_export]
macro_rules! dispatch {
(|$cpu:ident| $body:expr) => {
$crate::dispatch!($crate::isa::detect(), |$cpu| $body)
};
($level:expr, |$cpu:ident| $body:expr) => {
match $level {
$crate::isa::Level::Portable(t) => $crate::isa::Isa::run(t, |$cpu| $body),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
$crate::isa::Level::X86V3(t) => $crate::isa::Isa::run(t, |$cpu| $body),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
$crate::isa::Level::X86V4(t) => $crate::isa::Isa::run(t, |$cpu| $body),
$crate::isa::Level::Native(t) => $crate::isa::Isa::run(t, |$cpu| $body),
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
$crate::isa::Level::X86V2(t) => $crate::isa::Isa::run(t, |$cpu| $body),
}
};
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
#[allow(unsafe_code)]
pub trait X86Level: Isa {
const GFNI: bool;
#[doc(hidden)]
unsafe fn assume() -> Self;
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "ssse3",
not(feature = "portable")
))]
#[allow(unsafe_code)]
impl X86Level for Native {
const GFNI: bool = cfg!(target_feature = "gfni");
#[inline]
unsafe fn assume() -> Self {
Self
}
}