use core::arch::x86_64::*;
use core::sync::atomic::{AtomicU32, Ordering};
pub(crate) fn enter_cpu_state() -> u32 {
0
}
pub(crate) fn leave_cpu_state(_old: u32) {}
pub(in crate::low) fn zero_bytes(ptr: *mut u8, len: usize) {
match HaveAvx2::check() {
Some(_) => unsafe { zero_bytes_avx2(ptr, len) },
None => unsafe { zero_bytes_sse2(ptr, len) },
}
}
#[target_feature(enable = "avx,avx2")]
unsafe fn zero_bytes_avx2(ptr: *mut u8, len: usize) {
let _zero: __m256i;
unsafe {
core::arch::asm!(
" vpxor {zero}, {zero}, {zero}",
" 2: cmp {len}, 32",
" jl 3f",
" vmovdqu [{ptr}], {zero}",
" add {ptr}, 32",
" sub {len}, 32",
" jmp 2b",
" 3: sub {len}, 1",
" jl 4f",
" mov byte ptr [{ptr}], 0",
" add {ptr}, 1",
" jmp 3b",
" 4: ",
ptr = inout(reg) ptr => _,
len = inout(reg) len => _,
zero = out(ymm_reg) _zero,
)
}
}
unsafe fn zero_bytes_sse2(ptr: *mut u8, len: usize) {
unsafe {
core::arch::asm!(
" pxor {zero}, {zero}",
" 2: cmp {len}, 16",
" jl 3f",
" movdqu [{ptr}], {zero}",
" add {ptr}, 16",
" sub {len}, 16",
" jmp 2b",
" 3: sub {len}, 1",
" jl 4f",
" mov byte ptr [{ptr}], 0",
" add {ptr}, 1",
" jmp 3b",
" 4: ",
ptr = inout(reg) ptr => _,
len = inout(reg) len => _,
zero = out(xmm_reg) _,
)
}
}
pub(in crate::low) unsafe fn ct_compare_bytes(a: *const u8, b: *const u8, len: usize) -> u8 {
let mut acc = 0u8;
unsafe {
core::arch::asm!(
" 2: cmp {len}, 0",
" je 3f",
" mov {tmp}, [{a}]",
" xor {tmp}, [{b}]",
" or {acc}, {tmp}",
" add {a}, 1",
" add {b}, 1",
" sub {len}, 1",
" jmp 2b",
" 3: ",
a = inout(reg) a => _,
b = inout(reg) b => _,
len = inout(reg) len => _,
tmp = inout(reg_byte) 0u8 => _,
acc = inout(reg_byte) acc,
);
}
acc
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveAvx512ForAesGcm(());
impl HaveAvx512ForAesGcm {
pub(crate) fn check() -> Option<Self> {
match has(AES_GCM_BASE | AVX512F | AVX512BW | AVX512VL | VPCLMULQDQ | VAES) {
true => Some(Self(())),
false => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveVaesAvx2ForAesGcm(());
impl HaveVaesAvx2ForAesGcm {
pub(crate) fn check() -> Option<Self> {
match has(AES_GCM_BASE | VPCLMULQDQ | VAES) {
true => Some(Self(())),
false => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveAvx512ForMlKem(());
impl HaveAvx512ForMlKem {
pub(crate) fn check() -> Option<Self> {
match has(AVX2_KERNELS | AVX512F | AVX512BW | AVX512VBMI | AVX512VBMI2) {
true => Some(Self(())),
false => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveSha1(());
impl HaveSha1 {
pub(crate) fn check() -> Option<Self> {
match has(SHA | SSSE3 | SSE41) {
true => Some(Self(())),
false => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveSha256(());
impl HaveSha256 {
pub(crate) fn check() -> Option<Self> {
match has(SHA | SSSE3 | SSE41) {
true => Some(Self(())),
false => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveAvx2(());
impl HaveAvx2 {
pub(crate) fn check() -> Option<Self> {
has(AVX2_KERNELS).then_some(Self(()))
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveAesHardware(());
impl HaveAesHardware {
pub(crate) fn check() -> Option<Self> {
has(AES | PCLMULQDQ | SSSE3 | SSE41).then_some(Self(()))
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveAvx(());
impl HaveAvx {
pub(crate) fn check() -> Option<Self> {
has(AVX | SSSE3).then_some(Self(()))
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveSsse3(());
impl HaveSsse3 {
pub(crate) fn check() -> Option<Self> {
has(SSSE3).then_some(Self(()))
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct HaveBmi2Adx(());
impl HaveBmi2Adx {
pub(crate) fn check() -> Option<Self> {
has(BMI2 | ADX).then_some(Self(()))
}
}
pub(crate) fn is_intel() -> bool {
has(INTEL)
}
const AES: u32 = 1 << 0;
const VAES: u32 = 1 << 1;
const PCLMULQDQ: u32 = 1 << 2;
const VPCLMULQDQ: u32 = 1 << 3;
const BMI2: u32 = 1 << 4;
const ADX: u32 = 1 << 5;
const POPCNT: u32 = 1 << 6;
const SSSE3: u32 = 1 << 7;
const AVX: u32 = 1 << 8;
const AVX2: u32 = 1 << 9;
const AVX512F: u32 = 1 << 10;
const AVX512BW: u32 = 1 << 11;
const AVX512VL: u32 = 1 << 12;
const AVX512VBMI: u32 = 1 << 13;
const AVX512VBMI2: u32 = 1 << 14;
const SHA: u32 = 1 << 15;
const SSE41: u32 = 1 << 16;
const INTEL: u32 = 1 << 30;
const AVX2_KERNELS: u32 = AVX | AVX2 | BMI2 | POPCNT | SSSE3;
const AES_GCM_BASE: u32 = AES | PCLMULQDQ | SSSE3 | AVX | AVX2;
const DETECTED: u32 = 1 << 31;
fn has(wanted: u32) -> bool {
features() & wanted == wanted
}
fn features() -> u32 {
static FEATURES: AtomicU32 = AtomicU32::new(0);
match FEATURES.load(Ordering::Relaxed) {
0 => {
let features = detect() | DETECTED;
FEATURES.store(features, Ordering::Relaxed);
features
}
features => features,
}
}
fn detect() -> u32 {
macro_rules! detect {
($name:tt, $bit:expr) => {
if test_toggle($name, is_x86_feature_detected!($name)) {
$bit
} else {
0
}
};
}
let id = __cpuid(0);
let intel = (id.ebx, id.edx, id.ecx) == (0x756e_6547, 0x4965_6e69, 0x6c65_746e);
let adx = is_x86_feature_detected!("adx") || option_env!("VALGRIND_BUG_494162").is_some();
(if test_toggle("intel", intel) {
INTEL
} else {
0
}) | (if test_toggle("adx", adx) { ADX } else { 0 })
| detect!("aes", AES)
| detect!("vaes", VAES)
| detect!("pclmulqdq", PCLMULQDQ)
| detect!("vpclmulqdq", VPCLMULQDQ)
| detect!("bmi2", BMI2)
| detect!("popcnt", POPCNT)
| detect!("ssse3", SSSE3)
| detect!("sse4.1", SSE41)
| detect!("avx", AVX)
| detect!("avx2", AVX2)
| detect!("avx512f", AVX512F)
| detect!("avx512bw", AVX512BW)
| detect!("avx512vl", AVX512VL)
| detect!("avx512vbmi", AVX512VBMI)
| detect!("avx512vbmi2", AVX512VBMI2)
| detect!("sha", SHA)
}
#[cfg(not(debug_assertions))]
fn test_toggle(_id: &str, detected: bool) -> bool {
detected
}
#[cfg(debug_assertions)]
fn test_toggle(id: &str, detected: bool) -> bool {
if std::env::var(format!("RIND_CPU_DISABLE_{id}")).is_ok() {
println!("DEBUG: denying cpuid {id:?}");
false
} else {
detected
}
}
pub(crate) fn missing_cpu_feature() -> Option<&'static str> {
None
}
#[target_feature(enable = "sse")]
#[inline]
pub(crate) fn prefetch<T>(table: &[T], stride: usize) {
unsafe {
_mm_prefetch(table.as_ptr().cast(), _MM_HINT_T0);
_mm_prefetch(table.as_ptr().add(stride).cast(), _MM_HINT_T0);
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn detects_the_expected_cpu() {
if let Some(expected) = std::env::var_os("RIND_TEST_X86_BMI2_ADX") {
assert_eq!(HaveBmi2Adx::check().is_some(), expected == "1");
}
if let Some(expected) = std::env::var_os("RIND_TEST_X86_AVX2") {
assert_eq!(HaveAvx2::check().is_some(), expected == "1");
}
if let Some(expected) = std::env::var_os("RIND_TEST_X86_AVX") {
assert_eq!(HaveAvx::check().is_some(), expected == "1");
}
if let Some(expected) = std::env::var_os("RIND_TEST_X86_AES_HARDWARE") {
assert_eq!(HaveAesHardware::check().is_some(), expected == "1");
}
if let Some(expected) = std::env::var_os("RIND_TEST_X86_SSSE3") {
assert_eq!(HaveSsse3::check().is_some(), expected == "1");
}
}
}