use std::env;
use force_target_features as target_features;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arch {
X86_64,
Aarch64,
Aarch64Be,
Riscv64,
Riscv32,
Ppc64le,
Arm,
Unknown,
}
impl Arch {
pub fn force_define(self) -> Option<&'static str> {
match self {
Arch::X86_64 => Some("MLD_FORCE_X86_64"),
Arch::Aarch64 => Some("MLD_FORCE_AARCH64"),
Arch::Aarch64Be => Some("MLD_FORCE_AARCH64_EB"),
Arch::Riscv64 => Some("MLD_FORCE_RISCV64"),
Arch::Riscv32 => Some("MLD_FORCE_RISCV32"),
Arch::Ppc64le => Some("MLD_FORCE_PPC64LE"),
Arch::Arm | Arch::Unknown => None,
}
}
}
pub type Define = (String, Option<String>);
#[derive(Debug, Clone, Default)]
pub struct Detection {
#[expect(dead_code)] pub arch: Option<Arch>,
pub defines: Vec<Define>,
pub flags: Vec<String>,
}
impl Detection {
pub fn apply(&self, build: &mut cc::Build) {
for (name, value) in &self.defines {
build.define(name, value.as_deref());
}
for flag in &self.flags {
build.flag(flag);
}
}
}
pub fn detect() -> Detection {
let arch = detect_arch();
println!("cargo:warning=(INFO) Architecture: `{arch:?}`");
let features = target_features(arch);
println!("cargo:warning=(INFO) {features:?}");
let mut defines: Vec<Define> = Vec::new();
let mut flags: Vec<String> = Vec::new();
if let Some(def) = arch.force_define() {
defines.push((def.to_string(), None));
}
match arch {
Arch::X86_64 => {
if features.has("avx2")
&& compiler_can_emit(
"MK_COMPILER_SUPPORTS_AVX2",
"-mavx2",
Some(
r#"int main() { __asm__("vpxor %%ymm0, %%ymm1, %%ymm2" ::: "ymm0", "ymm1", "ymm2"); return 0; }"#,
),
)
{
flags.push("-mavx2".into());
}
if features.has("bmi2")
&& compiler_can_emit(
"MK_COMPILER_SUPPORTS_BMI2",
"-mbmi2",
Some(
r#"int main() { __asm__("pdep %%eax, %%ebx, %%ecx" ::: "eax", "ebx", "ecx"); return 0; }"#,
),
)
{
flags.push("-mbmi2".into());
}
let _ = env_override("MK_COMPILER_SUPPORTS_SSE2");
}
Arch::Aarch64 => {
if features.has("sha3")
&& compiler_can_emit(
"MK_COMPILER_SUPPORTS_SHA3",
"-march=armv8.4-a+sha3",
Some(
r#"int main() { __asm__("eor3 v0.16b, v1.16b, v2.16b, v3.16b" ::: "v0", "v1", "v2", "v3"); return 0; }"#,
),
)
{
flags.push("-march=armv8.4-a+sha3".into());
}
}
Arch::Riscv64 => {
if features.has("v")
&& compiler_can_emit(
"MK_COMPILER_SUPPORTS_RVV",
"-march=rv64gcv",
Some(r#"int main() { __asm__("vadd.vv v0, v1, v2"); return 0; }"#),
)
{
flags.push("-march=rv64gcv".into());
}
}
Arch::Aarch64Be | Arch::Riscv32 | Arch::Ppc64le | Arch::Arm | Arch::Unknown => {}
}
Detection {
arch: Some(arch),
defines,
flags,
}
}
pub fn detect_arch() -> Arch {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH")
.expect("CARGO_CFG_TARGET_ARCH not set (run as a Cargo build script)");
let target_endian = env::var("CARGO_CFG_TARGET_ENDIAN").unwrap_or_default();
match target_arch.as_str() {
"x86_64" => Arch::X86_64,
"aarch64" => {
if target_endian == "big" {
Arch::Aarch64Be
} else {
Arch::Aarch64
}
}
"riscv64" => Arch::Riscv64,
"riscv32" => Arch::Riscv32,
"powerpc64" if target_endian == "little" => Arch::Ppc64le,
"arm" => Arch::Arm,
_ => Arch::Unknown,
}
}
#[derive(Debug)]
struct TargetFeatures {
set: Vec<String>,
}
impl TargetFeatures {
const EMPTY: Self = Self { set: Vec::new() };
fn new<I, S>(features: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
set: features.into_iter().map(Into::into).collect(),
}
}
fn has(&self, feat: &str) -> bool {
self.set.iter().any(|f| f == feat)
}
}
#[allow(dead_code)]
fn detect_target_features(_arch: Arch) -> TargetFeatures {
let raw = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
let set = raw
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
TargetFeatures { set }
}
#[allow(dead_code)]
fn force_target_features(arch: Arch) -> TargetFeatures {
match arch {
Arch::X86_64 => TargetFeatures::new(["avx2", "bmi2", "popcnt"]),
Arch::Aarch64 => TargetFeatures::new(["sha3"]),
Arch::Riscv64 => TargetFeatures::new(["v"]),
Arch::Aarch64Be | Arch::Riscv32 | Arch::Ppc64le | Arch::Arm | Arch::Unknown => {
TargetFeatures::EMPTY
}
}
}
fn env_override(name: &str) -> Option<bool> {
println!("cargo:rerun-if-env-changed={}", name);
env::var(name).ok().map(|v| v.trim() == "1")
}
fn compiler_can_emit(override_var: &str, flag: &str, asm_probe: Option<&str>) -> bool {
if let Some(v) = env_override(override_var) {
return v;
}
match cc::Build::new().is_flag_supported(flag) {
Ok(true) => {}
Ok(false) => {
println!(
"cargo:warning=build_native_detect: C compiler does not accept `{}`; skipping it",
flag
);
return false;
}
Err(e) => {
println!(
"cargo:warning=build_native_detect: could not probe flag `{}` ({}); skipping it",
flag, e
);
return false;
}
}
if let Some(probe) = asm_probe {
if !compiler_can_assemble(flag, probe) {
println!(
"cargo:warning=build_native_detect: C compiler accepts `{}` but cannot assemble \
the corresponding instructions; skipping it",
flag
);
return false;
}
}
true
}
fn compiler_can_assemble(flag: &str, asm_probe: &str) -> bool {
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| ".".into());
let src_path = format!("{}/mld_emit_probe.c", out_dir);
if std::fs::write(&src_path, asm_probe).is_err() {
return false;
}
cc::Build::new()
.file(&src_path)
.flag(flag)
.cargo_metadata(false)
.cargo_warnings(false)
.try_compile_intermediates()
.is_ok()
}