Skip to content

Fix has_cpuid implementation #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 25, 2018
75 changes: 45 additions & 30 deletions coresimd/x86/cpuid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,50 @@ pub fn has_cpuid() -> bool {
}
#[cfg(target_arch = "x86")]
{
use coresimd::x86::{__readeflags, __writeeflags};

// On `x86` the `cpuid` instruction is not always available.
// This follows the approach indicated in:
// http://wiki.osdev.org/CPUID#Checking_CPUID_availability
unsafe {
// Read EFLAGS:
let eflags: u32 = __readeflags();

// Invert the ID bit in EFLAGS:
let eflags_mod: u32 = eflags | 0x0020_0000;

// Store the modified EFLAGS (ID bit may or may not be inverted)
__writeeflags(eflags_mod);

// Read EFLAGS again:
let eflags_after: u32 = __readeflags();

// Check if the ID bit changed:
eflags_after != eflags
// On `x86` the `cpuid` instruction is not always available.
// This follows the approach indicated in:
// http://wiki.osdev.org/CPUID#Checking_CPUID_availability
// https://software.intel.com/en-us/articles/using-cpuid-to-detect-the-presence-of-sse-41-and-sse-42-instruction-sets/
// which detects whether `cpuid` is available by checking whether the 21st bit of the EFLAGS register is modifiable or not.
// If it is, then `cpuid` is available.
let result: u32;
let _temp: u32;
asm!(r#"
# Read eflags into $0 and copy it into $1:
pushfd
pop $0
mov $1, $0
# Flip 21st bit of $0.
xor $0, 0x200000
# Set eflags to the value of $0
#
# Bit 21st can only be modified if cpuid is available
push $0
popfd # A
# Read eflags into $0:
pushfd # B
pop $0
# xor with the original eflags sets the bits that
# have been modified:
xor $0, $1
"#
: "=r"(result), "=r"(_temp)
:
: "cc", "memory"
: "intel");
// There is a race between popfd (A) and pushfd (B)
// where other bits beyond 21st may have been modified due to
// interrupts, a debugger stepping through the asm, etc.
//
// Therefore, explicitly check whether the 21st bit
// was modified or not.
//
// If the result is zero, the cpuid bit was not modified.
// If the result is 0x200000 (non-zero), then the cpuid
// was correctly modified and the CPU supports the cpuid
// instruction:
(result & 0x200000) != 0
}
}
}
Expand Down Expand Up @@ -138,17 +162,8 @@ mod tests {
assert!(cpuid::has_cpuid());
}

#[cfg(target_arch = "x86")]
#[test]
fn test_has_cpuid() {
unsafe {
let before = __readeflags();

if cpuid::has_cpuid() {
assert!(before != __readeflags());
} else {
assert!(before == __readeflags());
}
}
fn test_has_cpuid_idempotent() {
assert_eq!(cpuid::has_cpuid(), cpuid::has_cpuid());
}
}