#![allow(unexpected_cfgs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum InstructionSet {
Scalar,
#[cfg(feature = "std_simd")]
StdSimd,
Unknown,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V1,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V2,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V3,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V4,
#[cfg(target_arch = "aarch64")]
NEON,
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
WASM32,
#[cfg(all(feature = "wasm", target_arch = "wasm64"))]
WASM64,
#[cfg(all(feature = "spirv", target_arch = "spirv"))]
SPIRV,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86_detector;
impl InstructionSet {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn get() -> InstructionSet {
static DETECTOR: x86_detector::DetectInstructionSet = x86_detector::DetectInstructionSet::new();
DETECTOR.get_or_init()
}
#[cfg(target_arch = "aarch64")]
pub fn get() -> InstructionSet {
InstructionSet::NEON
}
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
pub fn get() -> InstructionSet {
InstructionSet::WASM32
}
#[cfg(all(feature = "wasm", target_arch = "wasm64"))]
pub fn get() -> InstructionSet {
InstructionSet::WASM64
}
#[cfg(all(feature = "spirv", target_arch = "spirv"))]
pub fn get() -> InstructionSet {
InstructionSet::SPIRV
}
#[cfg(not(any(
any(target_arch = "x86", target_arch = "x86_64"),
target_arch = "aarch64",
all(feature = "wasm", any(target_arch = "wasm32", target_arch = "wasm64")),
all(feature = "spirv", target_arch = "spirv"),
)))]
pub fn get() -> InstructionSet {
InstructionSet::Scalar
}
#[inline(always)]
pub const fn min(a: InstructionSet, b: InstructionSet) -> InstructionSet {
if (a as u8) < (b as u8) { a } else { b }
}
#[inline(always)]
pub const fn max(a: InstructionSet, b: InstructionSet) -> InstructionSet {
if (a as u8) > (b as u8) { a } else { b }
}
#[inline(always)]
pub const fn assert_eq(a: InstructionSet, b: InstructionSet) -> InstructionSet {
assert!((a as u8) == (b as u8), "InstructionSet equality assertion failed");
a
}
#[inline(always)]
pub const fn has_instruction_level_parallelism(self) -> bool {
cfg!(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm",
target_arch = "aarch64"
))
}
}
macro_rules! isa_properties {
($(
$(#[cfg $cfg:tt])?
$variant:ident {
registers: $registers:expr,
fma: $fma:expr,
simd: $simd:expr,
unaligned_cheap: $unaligned:expr,
unroll: $unroll:expr,
masked: $masked:expr,
}
)*) => {
impl InstructionSet {
#[inline(always)]
pub const fn num_registers(self) -> usize {
match self { $( $(#[cfg $cfg])? Self::$variant => $registers, )* }
}
#[inline(always)]
pub const fn has_fma(self) -> bool {
match self { $( $(#[cfg $cfg])? Self::$variant => $fma, )* }
}
#[inline(always)]
pub const fn is_simd(self) -> bool {
match self { $( $(#[cfg $cfg])? Self::$variant => $simd, )* }
}
#[inline(always)]
pub const fn unaligned_is_cheap(self) -> bool {
match self { $( $(#[cfg $cfg])? Self::$variant => $unaligned, )* }
}
#[inline(always)]
pub const fn unroll_factor(self) -> usize {
match self { $( $(#[cfg $cfg])? Self::$variant => $unroll, )* }
}
#[inline(always)]
pub const fn has_masked_operations(self) -> bool {
match self { $( $(#[cfg $cfg])? Self::$variant => $masked, )* }
}
}
};
}
isa_properties! {
Scalar {
registers: 1, fma: false, simd: false, unaligned_cheap: true, unroll: 4, masked: false,
}
#[cfg(feature = "std_simd")]
StdSimd {
registers: 1, fma: false, simd: true, unaligned_cheap: false, unroll: 1, masked: false,
}
Unknown {
registers: 1, fma: false, simd: false, unaligned_cheap: false, unroll: 1, masked: false,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V1 {
registers: 8, fma: false, simd: true, unaligned_cheap: false, unroll: 4, masked: false,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V2 {
registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 4, masked: false,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V3 {
registers: 16, fma: true, simd: true, unaligned_cheap: true, unroll: 4, masked: false,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
X86V4 {
registers: 32, fma: true, simd: true, unaligned_cheap: true, unroll: 8, masked: true,
}
#[cfg(target_arch = "aarch64")]
NEON {
registers: 32, fma: true, simd: true, unaligned_cheap: true, unroll: 4, masked: false,
}
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
WASM32 {
registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 2, masked: false,
}
#[cfg(all(feature = "wasm", target_arch = "wasm64"))]
WASM64 {
registers: 16, fma: false, simd: true, unaligned_cheap: false, unroll: 2, masked: false,
}
#[cfg(all(feature = "spirv", target_arch = "spirv"))]
SPIRV {
registers: 1, fma: true, simd: false, unaligned_cheap: true, unroll: 1, masked: false,
}
}
#[cfg(test)]
mod tests {
use super::InstructionSet;
fn all() -> &'static [InstructionSet] {
&[
InstructionSet::Scalar,
InstructionSet::Unknown,
#[cfg(feature = "std_simd")]
InstructionSet::StdSimd,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
InstructionSet::X86V1,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
InstructionSet::X86V2,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
InstructionSet::X86V3,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
InstructionSet::X86V4,
#[cfg(target_arch = "aarch64")]
InstructionSet::NEON,
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
InstructionSet::WASM32,
#[cfg(all(feature = "wasm", target_arch = "wasm64"))]
InstructionSet::WASM64,
]
}
#[test]
fn properties_are_sane() {
for &isa in all() {
assert!(isa.num_registers() >= 1, "{isa:?}: zero registers");
assert!(isa.unroll_factor() >= 1, "{isa:?}: zero unroll factor");
assert!(
!isa.has_masked_operations() || isa.is_simd(),
"{isa:?}: masked but not SIMD"
);
}
assert!(!InstructionSet::Scalar.is_simd());
assert!(!InstructionSet::Unknown.is_simd());
assert!(!InstructionSet::Scalar.has_fma());
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn x86_rows() {
use InstructionSet::{X86V1, X86V2, X86V3, X86V4};
assert_eq!((X86V1.num_registers(), X86V2.num_registers()), (8, 16));
assert_eq!((X86V3.num_registers(), X86V4.num_registers()), (16, 32));
assert!(!X86V1.has_fma() && !X86V2.has_fma() && X86V3.has_fma() && X86V4.has_fma());
assert!(!X86V1.unaligned_is_cheap() && !X86V2.unaligned_is_cheap());
assert!(X86V3.unaligned_is_cheap() && X86V4.unaligned_is_cheap());
assert!(!X86V1.has_masked_operations() && !X86V2.has_masked_operations());
assert!(!X86V3.has_masked_operations() && X86V4.has_masked_operations());
assert_eq!(X86V3.unroll_factor(), 4);
assert_eq!(X86V4.unroll_factor(), 8);
assert!(InstructionSet::Scalar < X86V1 && X86V1 < X86V2 && X86V2 < X86V3 && X86V3 < X86V4);
assert_eq!(InstructionSet::min(X86V2, X86V4), X86V2);
assert_eq!(InstructionSet::max(X86V2, X86V4), X86V4);
assert_eq!(InstructionSet::assert_eq(X86V3, X86V3), X86V3);
}
#[test]
#[should_panic(expected = "InstructionSet equality assertion failed")]
fn assert_eq_rejects_mismatch() {
InstructionSet::assert_eq(InstructionSet::Scalar, InstructionSet::Unknown);
}
#[test]
fn ilp_does_not_vary_by_variant() {
let expected = InstructionSet::Scalar.has_instruction_level_parallelism();
for &isa in all() {
assert_eq!(isa.has_instruction_level_parallelism(), expected, "{isa:?}");
}
assert_eq!(
expected,
cfg!(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm",
target_arch = "aarch64"
))
);
}
#[test]
fn get_is_available() {
let isa = InstructionSet::get();
assert!(
all().contains(&isa) || isa == InstructionSet::Scalar,
"{isa:?} is not a compiled variant"
);
}
}