use std::cmp::Ordering;
use std::env;
use std::process::Command;
#[derive(PartialEq, Eq, Debug)]
struct CpuFeature {
name: &'static str,
rustc_flag: &'static str,
cfg_flag: &'static str,
detected: bool,
nightly_only: bool,
}
impl CpuFeature {
fn priority(&self) -> usize {
match self.name {
"avx512f" => 0,
"avx2" => 1,
"sse4_1" => 2,
_ => usize::MAX, }
}
fn features() -> Vec<CpuFeature> {
vec![
CpuFeature {
name: "sse4_1",
rustc_flag: "+sse4.1",
cfg_flag: "sse",
detected: false,
nightly_only: false,
},
CpuFeature {
name: "avx2",
rustc_flag: "+avx2,+avx",
cfg_flag: "avx2",
detected: false,
nightly_only: false,
},
CpuFeature {
name: "neon",
rustc_flag: "+neon",
cfg_flag: "neon",
detected: false,
nightly_only: false,
},
]
}
fn nightly_features() -> Vec<CpuFeature> {
vec![
CpuFeature {
name: "sse4_1",
rustc_flag: "+sse4.1",
cfg_flag: "sse",
detected: false,
nightly_only: false,
},
CpuFeature {
name: "avx512f",
rustc_flag: "+avx512f",
cfg_flag: "avx512",
detected: false,
nightly_only: true,
},
CpuFeature {
name: "avx2",
rustc_flag: "+avx2,+avx",
cfg_flag: "avx2",
detected: false,
nightly_only: false,
},
CpuFeature {
name: "neon",
rustc_flag: "+neon",
cfg_flag: "neon",
detected: false,
nightly_only: false,
},
]
}
}
impl Ord for CpuFeature {
fn cmp(&self, other: &Self) -> Ordering {
self.priority().cmp(&other.priority())
}
}
impl PartialOrd for CpuFeature {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
trait CpuFeatureDetector {
fn detect_features(&self, features: &mut [CpuFeature]);
fn is_applicable(&self) -> bool;
}
struct LinuxDetector;
impl CpuFeatureDetector for LinuxDetector {
fn detect_features(&self, features: &mut [CpuFeature]) {
if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
let contents = cpuinfo.to_lowercase();
for feature in features.iter_mut() {
feature.detected = contents.contains(feature.name);
}
}
}
fn is_applicable(&self) -> bool {
cfg!(target_os = "linux")
}
}
struct MacOSDetector;
impl CpuFeatureDetector for MacOSDetector {
fn detect_features(&self, features: &mut [CpuFeature]) {
let output = Command::new("sysctl").args(["-a"]).output();
if let Ok(output) = output {
let contents = String::from_utf8_lossy(&output.stdout).to_lowercase();
for feature in features.iter_mut() {
match feature.name {
"avx512f" => feature.detected = contents.contains("hw.optional.avx512f: 1"),
"avx2" => feature.detected = contents.contains("hw.optional.avx2: 1"),
"sse4_1" => feature.detected = contents.contains("hw.optional.sse4_1: 1"),
"neon" => feature.detected = contents.contains("hw.optional.neon: 1"),
_ => {}
}
}
}
}
fn is_applicable(&self) -> bool {
cfg!(target_os = "macos")
}
}
struct PlatformDetector;
impl PlatformDetector {
fn cpu_features_detectors() -> Vec<Box<dyn CpuFeatureDetector>> {
vec![Box::new(LinuxDetector), Box::new(MacOSDetector)]
}
fn compiler_channel() -> String {
let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
let output = Command::new(rustc)
.args(["--version", "--verbose"])
.output()
.expect("Failed to execute rustc");
let version_info = String::from_utf8_lossy(&output.stdout);
if version_info.contains("nightly") {
"nightly".to_string()
} else {
"stable".to_string()
}
}
fn detect_cpu_features(features: &mut [CpuFeature]) {
let detectors = Self::cpu_features_detectors();
for detector in detectors {
if detector.is_applicable() {
detector.detect_features(features);
break;
}
}
}
fn apply(features: &mut [CpuFeature]) {
features.sort();
let cfg_flag = features
.iter()
.find(|cpu_feature| cpu_feature.detected)
.map(|cpu_feature| {
println!("cargo:rustc-flag=-C");
println!("cargo:rustc-flag=target-feature={}", cpu_feature.rustc_flag);
cpu_feature.cfg_flag
})
.unwrap_or_else(|| "fallback");
println!("applying: {cfg_flag}");
println!("cargo:rustc-cfg={cfg_flag}");
println!("cargo::rustc-check-cfg=cfg(avx512)");
println!("cargo::rustc-check-cfg=cfg(avx2)");
println!("cargo::rustc-check-cfg=cfg(sse)");
println!("cargo::rustc-check-cfg=cfg(neon)");
println!("cargo::rustc-check-cfg=cfg(fallback)");
}
}
fn main() {
let rustc_channel = PlatformDetector::compiler_channel();
println!("cargo:rustc-cfg=rustc_channel=\"{rustc_channel}\"");
println!("cargo::rustc-check-cfg=cfg(rustc_channel, values(\"nightly\", \"stable\"))");
let nightly_build = rustc_channel == "nightly";
let mut features = if nightly_build {
CpuFeature::nightly_features()
} else {
CpuFeature::features()
};
let host = env::var("HOST").unwrap_or_default();
let target = env::var("TARGET").unwrap_or_default();
let is_native_build = host == target;
if is_native_build {
PlatformDetector::detect_cpu_features(&mut features);
}
PlatformDetector::apply(&mut features);
}