diff --git a/src/runtime/aarch64.rs b/src/runtime/aarch64.rs index 5d10fc06e3..273c314d21 100644 --- a/src/runtime/aarch64.rs +++ b/src/runtime/aarch64.rs @@ -32,11 +32,11 @@ pub enum __Feature { } pub fn detect_features(mut x: T) -> usize { - let value: usize = 0; + let mut value: usize = 0; { let mut enable_feature = |f| { if x.has_feature(&f) { - bit::set(value, f as u32); + value = bit::set(value, f as u32); } }; enable_feature(__Feature::asimd); @@ -45,6 +45,25 @@ pub fn detect_features(mut x: T) -> usize { value } +/// Probe the ELF Auxiliary vector for hardware capabilities +/// +/// The values are part of the platform-specific [asm/hwcap.h][hwcap] +/// +/// [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h +impl linux::FeatureQuery for linux::AuxVec { + fn has_feature(&mut self, x: &__Feature) -> bool { + use self::__Feature::*; + if let Some(caps) = self.lookup(linux::AT::HWCAP) { + match *x { + asimd => caps & (1 << 1) != 0, + pmull => caps & (1 << 4) != 0, + } + } else { + false + } + } +} + impl linux::FeatureQuery for linux::CpuInfo { fn has_feature(&mut self, x: &__Feature) -> bool { use self::__Feature::*; diff --git a/src/runtime/arm.rs b/src/runtime/arm.rs index 60ef909fca..f9a71a044f 100644 --- a/src/runtime/arm.rs +++ b/src/runtime/arm.rs @@ -29,11 +29,11 @@ pub enum __Feature { } pub fn detect_features(mut x: T) -> usize { - let value: usize = 0; + let mut value: usize = 0; { let mut enable_feature = |f| { if x.has_feature(&f) { - bit::set(value, f as u32); + value = bit::set(value, f as u32); } }; enable_feature(__Feature::neon); @@ -42,6 +42,25 @@ pub fn detect_features(mut x: T) -> usize { value } +/// Probe the ELF Auxiliary vector for hardware capabilities +/// +/// The values are part of the platform-specific [asm/hwcap.h][hwcap] +/// +/// [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h +impl linux::FeatureQuery for linux::AuxVec { + fn has_feature(&mut self, x: &__Feature) -> bool { + use self::__Feature::*; + match *x { + neon => self.lookup(linux::AT::HWCAP) + .map(|caps| caps & (1 << 12) != 0) + .unwrap_or(false), + pmull => self.lookup(linux::AT::HWCAP2) + .map(|caps| caps & (1 << 1) != 0) + .unwrap_or(false), + } + } +} + /// Is the CPU known to have a broken NEON unit? /// /// See https://crbug.com/341598. diff --git a/src/runtime/linux/auxvec.rs b/src/runtime/linux/auxvec.rs new file mode 100644 index 0000000000..657d74e25d --- /dev/null +++ b/src/runtime/linux/auxvec.rs @@ -0,0 +1,89 @@ +//! Reads /proc/self/auxv on Linux systems + +use std::prelude::v1::*; +use std::slice; +use std::mem; + +/// Simple abstraction for the ELF Auxiliary Vector +/// +/// the elf.h provide the layout of the single entry as auxv_t. +/// The desugared version is a usize tag followed by a union with +/// the same storage size. +/// +/// Cache only the HWCAP and HWCAP2 entries. +#[derive(Debug)] +pub struct AuxVec { + hwcap: Option, + hwcap2: Option, +} + +#[derive(Clone, Debug, PartialEq)] +#[allow(dead_code)] +/// ELF Auxiliary vector entry types +/// +/// The entry types are specified in [linux/auxvec.h][auxvec_h]. +/// +/// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h +pub enum AT { + /// CPU Hardware capabilities, it is a bitfield. + HWCAP = 16, + /// CPU Hardware capabilities, additional bitfield. + HWCAP2 = 26, +} + +impl AuxVec { + /// Reads the ELF Auxiliary Vector + /// + /// Try to read `/proc/self/auxv`. + // TODO: Make use of getauxval once it is available in a + // reliable way. + pub fn new() -> Result { + use std::io::Read; + let mut file = ::std::fs::File::open("/proc/self/auxv")?; + let mut buf = [0usize; 64]; + let mut raw = unsafe { + slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut u8, + buf.len() * mem::size_of::(), + ) + }; + + let _ = file.read(&mut raw)?; + + mem::forget(raw); + + let mut auxv = AuxVec { hwcap: None, hwcap2: None }; + + for el in buf.chunks(2) { + if el[0] == AT::HWCAP as usize { + auxv.hwcap = Some(el[1]); + } + if el[0] == AT::HWCAP2 as usize { + auxv.hwcap2 = Some(el[1]); + } + } + + Ok(auxv) + } + + /// Returns the value for the AT key + pub fn lookup(&self, key: AT) -> Option { + match key { + AT::HWCAP => self.hwcap, + AT::HWCAP2 => self.hwcap2, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(target_os = "linux")] + #[test] + fn test_auxvec_linux() { + let auxvec = AuxVec::new().unwrap(); + println!("{:?}", auxvec.lookup(AT::HWCAP)); + println!("{:?}", auxvec); + } +} diff --git a/src/runtime/linux/cpuinfo.rs b/src/runtime/linux/cpuinfo.rs index 777be3de56..efdac7083f 100644 --- a/src/runtime/linux/cpuinfo.rs +++ b/src/runtime/linux/cpuinfo.rs @@ -214,4 +214,89 @@ CPU revision : 1"; assert!(cpuinfo.field("Features").has("neon")); assert!(cpuinfo.field("Features").has("asimd")); } + + const POWER8E_POWERKVM: &str = r"processor : 0 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 1 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 2 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +processor : 3 +cpu : POWER8E (raw), altivec supported +clock : 3425.000000MHz +revision : 2.1 (pvr 004b 0201) + +timebase : 512000000 +platform : pSeries +model : IBM pSeries (emulated by qemu) +machine : CHRP IBM pSeries (emulated by qemu)"; + + #[test] + fn test_cpuinfo_linux_power8_powerkvm() { + let cpuinfo = CpuInfo::from_str(POWER8E_POWERKVM).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER8E (raw), altivec supported"); + + assert!(cpuinfo.field("cpu").has("altivec")); + } + + const POWER5P: &str = r"processor : 0 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 1 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 2 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 3 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 4 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 5 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 6 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +processor : 7 +cpu : POWER5+ (gs) +clock : 1900.098000MHz +revision : 2.1 (pvr 003b 0201) + +timebase : 237331000 +platform : pSeries +machine : CHRP IBM,9133-55A"; + + #[test] + fn test_cpuinfo_linux_power5p() { + let cpuinfo = CpuInfo::from_str(POWER5P).unwrap(); + assert_eq!(cpuinfo.field("cpu"), "POWER5+ (gs)"); + + assert!(!cpuinfo.field("cpu").has("altivec")); + } } diff --git a/src/runtime/linux/mod.rs b/src/runtime/linux/mod.rs index 6625152baf..de7ebfbaa7 100644 --- a/src/runtime/linux/mod.rs +++ b/src/runtime/linux/mod.rs @@ -2,6 +2,9 @@ mod cpuinfo; pub use self::cpuinfo::CpuInfo; +mod auxvec; +pub use self::auxvec::*; + use super::__Feature; pub trait FeatureQuery { @@ -17,11 +20,18 @@ fn detect_features_impl(x: T) -> usize { { super::aarch64::detect_features(x) } + #[cfg(target_arch = "powerpc64")] + { + super::powerpc64::detect_features(x) + } } -/// Detects ARM features: +/// Detects CPU features: pub fn detect_features() -> usize { - // FIXME: use libc::getauxval, and if that fails /proc/auxv + // Try to read the ELF Auxiliary Vector + if let Ok(v) = auxvec::AuxVec::new() { + return detect_features_impl(v); + } // Try to read /proc/cpuinfo if let Ok(v) = cpuinfo::CpuInfo::new() { return detect_features_impl(v); diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 2ab5e246c1..4867ec95ff 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -17,12 +17,18 @@ mod aarch64; #[cfg(all(target_arch = "aarch64", target_os = "linux"))] pub use self::aarch64::__Feature; +#[cfg(all(target_arch = "powerpc64", target_os = "linux"))] +#[macro_use] +mod powerpc64; +#[cfg(all(target_arch = "powerpc64", target_os = "linux"))] +pub use self::powerpc64::__Feature; + #[cfg(all(target_os = "linux", - any(target_arch = "arm", target_arch = "aarch64")))] + any(target_arch = "arm", target_arch = "aarch64", target_arch = "powerpc64")))] mod linux; #[cfg(all(target_os = "linux", - any(target_arch = "arm", target_arch = "aarch64")))] + any(target_arch = "arm", target_arch = "aarch64", target_arch = "powerpc64")))] pub use self::linux::detect_features; /// Performs run-time feature detection. diff --git a/src/runtime/powerpc64.rs b/src/runtime/powerpc64.rs new file mode 100644 index 0000000000..0d6ebffbf6 --- /dev/null +++ b/src/runtime/powerpc64.rs @@ -0,0 +1,94 @@ +//! Run-time feature detection on PowerPC64. +use super::{bit, linux}; + +#[macro_export] +#[doc(hidden)] +macro_rules! __unstable_detect_feature { + ("altivec") => { + $crate::vendor::__unstable_detect_feature($crate::vendor::__Feature::altivec{}) + }; + ("vsx") => { + $crate::vendor::__unstable_detect_feature($crate::vendor::__Feature::vsx{}) + }; + ("power8") => { + $crate::vendor::__unstable_detect_feature($crate::vendor::__Feature::power8{}) + }; + ($t:tt) => { compile_error!(concat!("unknown PowerPC target feature: ", $t)) }; +} + +/// PowerPC CPU Feature enum. Each variant denotes a position in a bitset +/// for a particular feature. +/// +/// PLEASE: do not use this, it is an implementation detail subject to change. +#[doc(hidden)] +#[allow(non_camel_case_types)] +#[repr(u8)] +pub enum __Feature { + /// Altivec + altivec, + /// VSX + vsx, + /// Power8 + power8, +} + +pub fn detect_features(mut x: T) -> usize { + let mut value: usize = 0; + { + let mut enable_feature = |f| { + if x.has_feature(&f) { + value = bit::set(value, f as u32); + } + }; + enable_feature(__Feature::altivec); + enable_feature(__Feature::vsx); + enable_feature(__Feature::power8); + } + value +} + +/// Probe the ELF Auxiliary vector for hardware capabilities +/// +/// The values are part of the platform-specific [asm/cputable.h][cputable] +/// +/// [cputable]: https://github.com/torvalds/linux/blob/master/arch/powerpc/include/uapi/asm/cputable.h +impl linux::FeatureQuery for linux::AuxVec { + fn has_feature(&mut self, x: &__Feature) -> bool { + use self::__Feature::*; + match *x { + altivec => self.lookup(linux::AT::HWCAP) + .map(|caps| caps & 0x10000000 != 0) + .unwrap_or(false), + vsx => self.lookup(linux::AT::HWCAP) + .map(|caps| caps & 0x00000080 != 0) + .unwrap_or(false), + power8 => self.lookup(linux::AT::HWCAP2) + .map(|caps| caps & 0x80000000 != 0) + .unwrap_or(false), + } + } +} + +/// Check for altivec support only +/// +/// PowerPC's /proc/cpuinfo lacks a proper Feature field, +/// but `altivec` support is indicated in the `cpu` field. +impl linux::FeatureQuery for linux::CpuInfo { + fn has_feature(&mut self, x: &__Feature) -> bool { + use self::__Feature::*; + match *x { + altivec => self.field("cpu").has("altivec"), + _ => false + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn detect_feature() { + println!("altivec {}", __unstable_detect_feature!("altivec")); + println!("vsx {}", __unstable_detect_feature!("vsx")); + println!("power8 {}", __unstable_detect_feature!("power8")); + } +}