#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VariableShiftEncoding {
Bmi2,
LegacyCl,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StateBaseStrategy {
R15,
#[cfg_attr(
not(any(all(target_os = "windows", target_arch = "x86_64"), test)),
expect(dead_code, reason = "constructed by Windows x86-64 target detection")
)]
Fs,
#[cfg_attr(
not(any(all(target_os = "linux", target_arch = "x86_64"), test)),
expect(
dead_code,
reason = "constructed by Linux x86-64 target detection and emitter tests"
)
)]
Gs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct X86Features {
bmi2: bool,
avx: bool,
popcnt: bool,
state_base: StateBaseStrategy,
}
impl X86Features {
pub(crate) fn detect() -> Self {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let bmi2 = std::arch::is_x86_feature_detected!("bmi2");
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
let bmi2 = false;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let avx = std::arch::is_x86_feature_detected!("avx");
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
let avx = false;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let popcnt = std::arch::is_x86_feature_detected!("popcnt");
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
let popcnt = false;
Self {
bmi2,
avx,
popcnt,
state_base: detect_state_base_strategy(),
}
}
pub(crate) const fn bmi2(self) -> bool {
self.bmi2
}
pub(crate) const fn avx(self) -> bool {
self.avx
}
pub(crate) const fn popcnt(self) -> bool {
self.popcnt
}
pub(crate) const fn variable_shift_encoding(self) -> VariableShiftEncoding {
if self.bmi2 {
VariableShiftEncoding::Bmi2
} else {
VariableShiftEncoding::LegacyCl
}
}
pub(crate) const fn state_base(self) -> StateBaseStrategy {
self.state_base
}
pub(crate) const fn allocatable_register_count(self) -> usize {
match self.state_base {
StateBaseStrategy::Fs | StateBaseStrategy::Gs => 15,
StateBaseStrategy::R15 => 14,
}
}
#[cfg(test)]
pub(crate) const fn for_test(bmi2: bool) -> Self {
Self::for_test_with_avx(bmi2, false)
}
#[cfg(test)]
pub(crate) const fn for_test_with_avx(bmi2: bool, avx: bool) -> Self {
Self {
bmi2,
avx,
popcnt: false,
state_base: StateBaseStrategy::R15,
}
}
#[cfg(test)]
pub(crate) const fn for_test_with_state_base(
bmi2: bool,
state_base: StateBaseStrategy,
) -> Self {
Self {
bmi2,
avx: false,
popcnt: false,
state_base,
}
}
}
pub fn detected_image_feature_bits() -> u8 {
image_feature_bits(X86Features::detect())
}
pub(crate) const IMAGE_FEATURE_BMI2: u8 = 1 << 0;
pub(crate) const IMAGE_FEATURE_AVX: u8 = 1 << 1;
pub(crate) const IMAGE_FEATURE_POPCNT: u8 = 1 << 4;
pub(crate) fn emitted_image_feature_bits(
features: X86Features,
uses_bmi2: bool,
uses_avx: bool,
uses_popcnt: bool,
) -> u8 {
let mut bits = image_feature_bits(features);
if !uses_bmi2 {
bits &= !IMAGE_FEATURE_BMI2;
}
if !uses_avx {
bits &= !IMAGE_FEATURE_AVX;
}
if uses_popcnt {
bits |= IMAGE_FEATURE_POPCNT;
} else {
bits &= !IMAGE_FEATURE_POPCNT;
}
bits
}
fn image_feature_bits(features: X86Features) -> u8 {
const FS_STATE_BASE: u8 = 1 << 2;
const GS_STATE_BASE: u8 = 1 << 3;
let mut bits = 0;
if features.bmi2() {
bits |= IMAGE_FEATURE_BMI2;
}
if features.avx() {
bits |= IMAGE_FEATURE_AVX;
}
if features.popcnt() {
bits |= IMAGE_FEATURE_POPCNT;
}
match features.state_base() {
StateBaseStrategy::Fs => bits |= FS_STATE_BASE,
StateBaseStrategy::Gs => bits |= GS_STATE_BASE,
StateBaseStrategy::R15 => {}
}
bits
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn detect_state_base_strategy() -> StateBaseStrategy {
const HWCAP2_FSGSBASE: libc::c_ulong = 1 << 1;
let cpu_supports_fsgsbase = std::arch::x86_64::__cpuid_count(7, 0).ebx & 1 != 0;
if cpu_supports_fsgsbase
&& unsafe { libc::getauxval(libc::AT_HWCAP2) } & HWCAP2_FSGSBASE != 0
{
StateBaseStrategy::Gs
} else {
StateBaseStrategy::R15
}
}
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
fn detect_state_base_strategy() -> StateBaseStrategy {
const PF_RDWRFSGSBASE_AVAILABLE: u32 = 22;
#[link(name = "kernel32")]
unsafe extern "system" {
fn IsProcessorFeaturePresent(processor_feature: u32) -> i32;
}
if unsafe { IsProcessorFeaturePresent(PF_RDWRFSGSBASE_AVAILABLE) } != 0 {
StateBaseStrategy::Fs
} else {
StateBaseStrategy::R15
}
}
#[cfg(not(any(
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "windows", target_arch = "x86_64")
)))]
const fn detect_state_base_strategy() -> StateBaseStrategy {
StateBaseStrategy::R15
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn image_features_distinguish_fs_and_gs_state_bases() {
let fs = image_feature_bits(X86Features::for_test_with_state_base(
false,
StateBaseStrategy::Fs,
));
let gs = image_feature_bits(X86Features::for_test_with_state_base(
false,
StateBaseStrategy::Gs,
));
let r15 = image_feature_bits(X86Features::for_test_with_state_base(
false,
StateBaseStrategy::R15,
));
assert_ne!(fs, gs);
assert_ne!(fs, r15);
assert_ne!(gs, r15);
}
}