use std::collections::HashMap;
use std::env;
fn main() {
let serial_flags = build_stringzilla();
if env::var("CARGO_FEATURE_CPUS").is_ok()
|| env::var("CARGO_FEATURE_CUDA").is_ok()
|| env::var("CARGO_FEATURE_ROCM").is_ok()
{
build_stringzillas(&serial_flags);
}
}
fn build_stringzilla() -> HashMap<String, bool> {
let mut flags = HashMap::<String, bool>::new();
let mut build = cc::Build::new();
build
.include("include")
.include("c/stringzilla") .warnings(false)
.define("SZ_DEBUG", "0")
.flag("-std=c99") .flag_if_supported("-fdiagnostics-color=always")
.flag_if_supported("-fPIC");
for flag in no_builtin_flags() {
build.flag(flag);
}
if env::var("CARGO_FEATURE_DYNAMIC_DISPATCH").is_ok() {
build.define("SZ_DYNAMIC_DISPATCH", "1");
build.files([
"c/stringzilla/runtime.c",
"c/stringzilla/compare.c",
"c/stringzilla/memory.c",
"c/stringzilla/hash.c",
"c/stringzilla/find.c",
"c/stringzilla/sort.c",
"c/stringzilla/intersect.c",
"c/stringzilla/utf8_runes.c",
"c/stringzilla/utf8_tokens.c",
"c/stringzilla/utf8_wordbreaks.c",
"c/stringzilla/utf8_graphemes.c",
"c/stringzilla/utf8_sentences.c",
"c/stringzilla/utf8_linebreaks.c",
"c/stringzilla/utf8_uncased_fold.c",
"c/stringzilla/utf8_norm.c",
"c/stringzilla/utf8_uncased.c",
]);
} else {
build.define("SZ_DYNAMIC_DISPATCH", "0");
build.define("SZ_EXPORT", "1");
let amalgam_path = std::path::Path::new(&env::var("OUT_DIR").unwrap_or_default()).join("sz_stringzilla.c");
std::fs::write(&amalgam_path, "#include <stringzilla/stringzilla.h>\n").expect("write amalgamation TU");
build.file(&amalgam_path);
}
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let target_endian = env::var("CARGO_CFG_TARGET_ENDIAN").unwrap_or_default();
let target_bits = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap_or_default();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let avoid_libc = target_os == "unknown" || target_os.is_empty();
build.define("SZ_AVOID_LIBC", if avoid_libc { "1" } else { "0" });
if target_endian == "big" {
build.define("SZ_IS_BIG_ENDIAN_", "1");
flags.insert("SZ_IS_BIG_ENDIAN_".to_string(), true);
} else {
build.define("SZ_IS_BIG_ENDIAN_", "0");
flags.insert("SZ_IS_BIG_ENDIAN_".to_string(), false);
}
if target_arch == "x86_64" && target_bits == "64" {
build.define("SZ_IS_64BIT_X86_", "1");
build.define("SZ_IS_64BIT_ARM_", "0");
flags.insert("SZ_IS_64BIT_X86_".to_string(), true);
flags.insert("SZ_IS_64BIT_ARM_".to_string(), false);
} else if target_arch == "aarch64" && target_bits == "64" {
build.define("SZ_IS_64BIT_X86_", "0");
build.define("SZ_IS_64BIT_ARM_", "1");
flags.insert("SZ_IS_64BIT_X86_".to_string(), false);
flags.insert("SZ_IS_64BIT_ARM_".to_string(), true);
} else {
build.define("SZ_IS_64BIT_X86_", "0");
build.define("SZ_IS_64BIT_ARM_", "0");
flags.insert("SZ_IS_64BIT_X86_".to_string(), false);
flags.insert("SZ_IS_64BIT_ARM_".to_string(), false);
}
let is_wasm = target_arch == "wasm32" || target_arch == "wasm64";
let dynamic_dispatch = env::var("CARGO_FEATURE_DYNAMIC_DISPATCH").is_ok();
let target_features: std::collections::HashSet<String> = env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap_or_default()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let runtime_detectable = dynamic_dispatch && probe_runtime_detection(avoid_libc);
let machine_tokens = if dynamic_dispatch { None } else { machine_capabilities() };
let mut tuned_beyond_description = false;
let mut gated_by_description = false;
for probe in isa_probes(&target_arch) {
let compilable = probe_isa(probe);
let described = probe.runs_on.iter().all(|f| target_features.contains(*f));
let runnable = machine_tokens
.as_ref()
.map_or(described, |tokens| tokens.contains(probe.token));
let default_on = compilable
&& if dynamic_dispatch {
runtime_detectable || described
} else {
runnable
};
let env_value = env::var(probe.define)
.ok()
.filter(|v| !v.trim().is_empty())
.map(|v| !matches!(v.trim().to_lowercase().as_str(), "0" | "false" | "off" | "no"));
let enabled = match env_value {
Some(false) => {
println!("cargo:warning=Disabled {} via environment variable", probe.define);
false
}
Some(true) if !compilable => {
println!(
"cargo:warning={} requested via environment variable, but this toolchain cannot compile {}; disabling",
probe.define, probe.probe_file
);
false
}
Some(true) => {
if !dynamic_dispatch && !runnable {
println!(
"cargo:warning={} forced on beyond what this target or machine supports; static dispatch may SIGILL at runtime",
probe.define
);
}
true
}
None => {
if described && !compilable {
println!(
"cargo:warning=The target declares support, but this toolchain cannot compile {}; building without {}",
probe.probe_file, probe.define
);
}
if !dynamic_dispatch && compilable {
tuned_beyond_description |= default_on && !described; gated_by_description |= !default_on && machine_tokens.is_none();
}
default_on
}
};
build.define(probe.define, if enabled { "1" } else { "0" });
flags.insert(probe.define.to_string(), enabled);
}
if tuned_beyond_description {
println!(
"cargo:warning=Static dispatch: tiers beyond the declared target features were enabled because \
this machine supports them. The binary is tuned to this machine and is NOT portable to older \
CPUs; pin `-C target-feature=…` or set `SZ_USE_X=0` for portable builds."
);
}
if gated_by_description {
println!(
"cargo:warning=Static dispatch: some SIMD tiers were disabled because the target description \
does not advertise them. Build with `RUSTFLAGS=\"-C target-cpu=native\"` (or `-C \
target-feature=+…`) to bake in the best tier for the deployment machine."
);
}
if is_wasm {
if *flags.get("SZ_USE_V128RELAXED").unwrap_or(&false) {
build.flag("-msimd128").flag("-mrelaxed-simd");
} else if *flags.get("SZ_USE_V128").unwrap_or(&false) {
build.flag("-msimd128");
}
}
build.compile("stringzilla");
println!("cargo:rerun-if-changed=c/stringzilla");
println!("cargo:rerun-if-changed=include/stringzilla");
println!("cargo:rerun-if-changed=probes");
for probe in isa_probes(&target_arch) {
println!("cargo:rerun-if-env-changed={}", probe.define);
}
println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_FEATURE");
flags
}
struct IsaProbe {
define: &'static str,
probe_file: &'static str,
gcc_flags: &'static [&'static str],
msvc_flags: &'static [&'static str],
token: &'static str,
runs_on: &'static [&'static str],
}
const ARM_PROBES: &[IsaProbe] = &[
IsaProbe {
define: "SZ_USE_SVE2AES",
probe_file: "probes/arm_sve2aes.c",
gcc_flags: &[],
msvc_flags: &[],
token: "sve2aes",
runs_on: &["neon", "sve", "sve2", "sve2-aes"],
},
IsaProbe {
define: "SZ_USE_SVE2",
probe_file: "probes/arm_sve2.c",
gcc_flags: &[],
msvc_flags: &[],
token: "sve2",
runs_on: &["neon", "sve", "sve2"],
},
IsaProbe {
define: "SZ_USE_SVE",
probe_file: "probes/arm_sve.c",
gcc_flags: &[],
msvc_flags: &[],
token: "sve",
runs_on: &["neon", "sve"],
},
IsaProbe {
define: "SZ_USE_NEONSHA",
probe_file: "probes/arm_neonsha.c",
gcc_flags: &[],
msvc_flags: &[],
token: "neonsha",
runs_on: &["neon", "sha2"],
},
IsaProbe {
define: "SZ_USE_NEONAES",
probe_file: "probes/arm_neonaes.c",
gcc_flags: &[],
msvc_flags: &[],
token: "neonaes",
runs_on: &["neon", "aes"],
},
IsaProbe {
define: "SZ_USE_NEON",
probe_file: "probes/arm_neon.c",
gcc_flags: &[],
msvc_flags: &[],
token: "neon",
runs_on: &["neon"],
},
];
const X86_PROBES: &[IsaProbe] = &[
IsaProbe {
define: "SZ_USE_ICELAKE",
probe_file: "probes/x86_icelake.c",
gcc_flags: &[],
msvc_flags: &[],
token: "icelake",
runs_on: &[
"sse4.2",
"aes",
"avx2",
"avx512f",
"avx512vl",
"avx512bw",
"avx512vbmi",
"avx512vbmi2",
"avx512vnni",
"vaes",
],
},
IsaProbe {
define: "SZ_USE_SKYLAKE",
probe_file: "probes/x86_skylake.c",
gcc_flags: &[],
msvc_flags: &[],
token: "skylake",
runs_on: &["sse4.2", "aes", "avx2", "avx512f", "avx512vl", "avx512bw"],
},
IsaProbe {
define: "SZ_USE_HASWELL",
probe_file: "probes/x86_haswell.c",
gcc_flags: &[],
msvc_flags: &[],
token: "haswell",
runs_on: &["sse4.2", "aes", "avx2"],
},
IsaProbe {
define: "SZ_USE_GOLDMONT",
probe_file: "probes/x86_goldmont.c",
gcc_flags: &[],
msvc_flags: &[],
token: "goldmont",
runs_on: &["sse3", "ssse3", "sse4.1", "sha"],
},
IsaProbe {
define: "SZ_USE_WESTMERE",
probe_file: "probes/x86_westmere.c",
gcc_flags: &[],
msvc_flags: &[],
token: "westmere",
runs_on: &["sse4.2", "aes"],
},
];
const WASM_PROBES: &[IsaProbe] = &[
IsaProbe {
define: "SZ_USE_V128RELAXED",
probe_file: "probes/wasm_v128relaxed.c",
gcc_flags: &["-msimd128", "-mrelaxed-simd"],
msvc_flags: &[],
token: "v128relaxed",
runs_on: &[],
},
IsaProbe {
define: "SZ_USE_V128",
probe_file: "probes/wasm_v128.c",
gcc_flags: &["-msimd128"],
msvc_flags: &[],
token: "v128",
runs_on: &[],
},
];
const RISCV_PROBES: &[IsaProbe] = &[
IsaProbe {
define: "SZ_USE_RVVCRYPTO",
probe_file: "probes/riscv_rvvcrypto.c",
gcc_flags: &[],
msvc_flags: &[],
token: "rvvcrypto",
runs_on: &["v", "zvkned", "zvknhb"],
},
IsaProbe {
define: "SZ_USE_RVV",
probe_file: "probes/riscv_rvv.c",
gcc_flags: &[],
msvc_flags: &[],
token: "rvv",
runs_on: &["v"],
},
];
const LOONGARCH_PROBES: &[IsaProbe] = &[IsaProbe {
define: "SZ_USE_LASX",
probe_file: "probes/loongarch_lasx.c",
gcc_flags: &[],
msvc_flags: &[],
token: "lasx",
runs_on: &["lasx"],
}];
const POWER_PROBES: &[IsaProbe] = &[IsaProbe {
define: "SZ_USE_POWERVSX",
probe_file: "probes/power_vsx.c",
gcc_flags: &[],
msvc_flags: &[],
token: "powervsx",
runs_on: &["vsx"],
}];
fn isa_probes(target_arch: &str) -> &'static [IsaProbe] {
match target_arch {
"arm" | "aarch64" => ARM_PROBES,
"x86_64" => X86_PROBES,
"wasm32" | "wasm64" => WASM_PROBES,
"riscv64" => RISCV_PROBES,
"loongarch64" => LOONGARCH_PROBES,
"powerpc64" => POWER_PROBES,
_ => &[],
}
}
fn probe_isa(probe: &IsaProbe) -> bool {
let out_dir = match env::var("OUT_DIR") {
Ok(dir) => std::path::PathBuf::from(dir),
Err(_) => return false,
};
let name = probe.probe_file.replace("probes/", "sz_probe_").replace(".c", "");
let mut build = cc::Build::new();
build.cargo_metadata(false).warnings(false);
let tool = build.get_compiler();
let mut command = tool.to_command();
if tool.is_like_msvc() {
command
.current_dir(&out_dir)
.arg("/nologo")
.arg("/c")
.arg(
std::path::Path::new(probe.probe_file)
.canonicalize()
.unwrap_or_else(|_| probe.probe_file.into()),
)
.arg(format!("/Fo{}", out_dir.join(format!("{name}.obj")).display()));
for flag in probe.msvc_flags {
command.arg(flag);
}
} else {
command
.arg("-std=c99") .arg("-c")
.arg(probe.probe_file)
.arg("-o")
.arg(out_dir.join(format!("{name}.o")));
for flag in probe.gcc_flags {
command.arg(flag);
}
}
command.output().map(|result| result.status.success()).unwrap_or(false)
}
fn probe_runtime_detection(avoid_libc: bool) -> bool {
let out_dir = match env::var("OUT_DIR") {
Ok(dir) => std::path::PathBuf::from(dir),
Err(_) => return false,
};
let manifest_dir = match env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => std::path::PathBuf::from(dir),
Err(_) => return false,
};
let source = manifest_dir.join("probes").join("runtime_detection.c");
let include_dir = manifest_dir.join("include");
let avoid_libc_define = format!("SZ_AVOID_LIBC={}", if avoid_libc { "1" } else { "0" });
let mut build = cc::Build::new();
build.cargo_metadata(false).warnings(false);
let tool = build.get_compiler();
let mut command = tool.to_command();
if tool.is_like_msvc() {
command
.current_dir(&out_dir)
.arg("/nologo")
.arg("/c")
.arg(&source)
.arg(format!("/I{}", include_dir.display()))
.arg(format!("/D{avoid_libc_define}"))
.arg(format!(
"/Fo{}",
out_dir.join("sz_probe_runtime_detection.obj").display()
));
} else {
command
.arg("-std=c99")
.arg(format!("-I{}", include_dir.display()))
.arg(format!("-D{avoid_libc_define}"))
.arg("-c")
.arg(&source)
.arg("-o")
.arg(out_dir.join("sz_probe_runtime_detection.o"));
}
command.output().map(|result| result.status.success()).unwrap_or(false)
}
fn machine_capabilities() -> Option<std::collections::HashSet<String>> {
let host = env::var("HOST").ok()?;
let target = env::var("TARGET").ok()?;
if host != target {
return None;
}
let out_dir = std::path::PathBuf::from(env::var("OUT_DIR").ok()?);
let manifest_dir = std::path::PathBuf::from(env::var("CARGO_MANIFEST_DIR").ok()?);
let source = manifest_dir.join("probes").join("run_capabilities.c");
let include_dir = manifest_dir.join("include");
let mut probe = cc::Build::new();
probe.cargo_metadata(false).warnings(false).opt_level(0);
let tool = probe.get_compiler();
let exe = out_dir.join(if tool.is_like_msvc() {
"sz_run_capabilities.exe"
} else {
"sz_run_capabilities"
});
let mut command = tool.to_command();
if tool.is_like_msvc() {
command.current_dir(&out_dir); command
.arg("/nologo")
.arg(&source)
.arg(format!("/I{}", include_dir.display()))
.arg(format!("/Fe{}", exe.display()));
} else {
command
.arg(&source)
.arg(format!("-I{}", include_dir.display()))
.arg("-o")
.arg(&exe);
}
let compiled = command.output().ok()?;
if !compiled.status.success() {
return None;
}
let ran = std::process::Command::new(&exe).output().ok()?;
if !ran.status.success() {
return None;
}
let stdout = String::from_utf8(ran.stdout).ok()?;
let tokens: std::collections::HashSet<String> = stdout
.trim()
.split(',')
.map(|token| token.trim().to_string())
.filter(|token| !token.is_empty())
.collect();
if tokens.is_empty() {
None
} else {
Some(tokens)
}
}
fn msvc_cxx_flags() -> &'static [&'static str] {
if matches!(env::var("CARGO_CFG_TARGET_ENV").as_deref(), Ok("msvc")) {
&["/Zc:__cplusplus", "/Zc:preprocessor", "/utf-8"]
} else {
&[]
}
}
fn no_builtin_flags() -> &'static [&'static str] {
if matches!(env::var("CARGO_CFG_TARGET_ENV").as_deref(), Ok("msvc")) {
&["/Oi-"]
} else {
&[
"-fno-builtin-memcmp",
"-fno-builtin-memchr",
"-fno-builtin-memcpy",
"-fno-builtin-memset",
]
}
}
fn stringzillas_base_build(serial_flags: &HashMap<String, bool>) -> cc::Build {
let mut build = cc::Build::new();
build
.include("include")
.warnings(false)
.define("SZ_DYNAMIC_DISPATCH", "1")
.define("SZ_AVOID_LIBC", "0")
.define("SZ_DEBUG", "0")
.std("c++20")
.flag_if_supported("-fdiagnostics-color=always")
.flag_if_supported("-fPIC");
build.include(std::env::var("DEP_FORKUNION_INCLUDE").expect("exported by the `forkunion` crate"));
for (flag, enabled) in serial_flags.iter() {
build.define(flag, if *enabled { "1" } else { "0" });
}
build
}
const STRINGZILLAS_API_CPP_SOURCES: [&str; 5] = [
"c/stringzillas/runtime.cpp",
"c/stringzillas/levenshtein.cpp",
"c/stringzillas/needleman_wunsch.cpp",
"c/stringzillas/smith_waterman.cpp",
"c/stringzillas/fingerprints.cpp",
];
const STRINGZILLAS_API_CU_SOURCES: [&str; 5] = [
"c/stringzillas/runtime.cu",
"c/stringzillas/levenshtein.cu",
"c/stringzillas/needleman_wunsch.cu",
"c/stringzillas/smith_waterman.cu",
"c/stringzillas/fingerprints.cu",
];
const STRINGZILLAS_CPUS_SOURCES: [&str; 15] = [
"c/stringzillas/levenshtein_serial.cpp",
"c/stringzillas/levenshtein_icelake.cpp",
"c/stringzillas/levenshtein_haswell.cpp",
"c/stringzillas/levenshtein_neon.cpp",
"c/stringzillas/levenshtein_rvv.cpp",
"c/stringzillas/needleman_wunsch_serial.cpp",
"c/stringzillas/needleman_wunsch_icelake.cpp",
"c/stringzillas/needleman_wunsch_haswell.cpp",
"c/stringzillas/needleman_wunsch_neon.cpp",
"c/stringzillas/needleman_wunsch_rvv.cpp",
"c/stringzillas/smith_waterman_serial.cpp",
"c/stringzillas/smith_waterman_icelake.cpp",
"c/stringzillas/smith_waterman_haswell.cpp",
"c/stringzillas/smith_waterman_neon.cpp",
"c/stringzillas/smith_waterman_rvv.cpp",
];
const STRINGZILLAS_CUDA_SOURCES: [&str; 3] = [
"c/stringzillas/levenshtein_cuda.cu",
"c/stringzillas/needleman_wunsch_cuda.cu",
"c/stringzillas/smith_waterman_cuda.cu",
];
const STRINGZILLAS_KEPLER_SOURCES: [&str; 1] = ["c/stringzillas/levenshtein_kepler.cu"];
const STRINGZILLAS_HOPPER_SOURCES: [&str; 3] = [
"c/stringzillas/levenshtein_hopper.cu",
"c/stringzillas/needleman_wunsch_hopper.cu",
"c/stringzillas/smith_waterman_hopper.cu",
];
fn try_build_stringzillas_cuda(serial_flags: &HashMap<String, bool>) -> Result<(), cc::Error> {
let mut build = stringzillas_base_build(serial_flags);
build.cuda(true).define("SZ_USE_CUDA", "1").define("SZ_USE_ROCM", "0");
if let Ok(host_cxx) = env::var("CUDAHOSTCXX") {
build.flag("-ccbin").flag(&host_cxx);
}
build.flag("-std=c++20").flag("--expt-relaxed-constexpr");
for gencode in [
"-gencode=arch=compute_80,code=sm_80",
"-gencode=arch=compute_90,code=sm_90",
"-gencode=arch=compute_90,code=compute_90",
] {
build.flag(gencode);
}
build.flag("-Xfatbin=--compress-all");
for flag in msvc_cxx_flags().iter().chain(no_builtin_flags()) {
build.flag(format!("-Xcompiler={flag}"));
}
build.files(STRINGZILLAS_API_CU_SOURCES);
build.files(STRINGZILLAS_CPUS_SOURCES);
build.files(STRINGZILLAS_CUDA_SOURCES);
build.files(STRINGZILLAS_KEPLER_SOURCES);
build.files(STRINGZILLAS_HOPPER_SOURCES);
build.try_compile("stringzillas")?;
if !matches!(env::var("CARGO_CFG_TARGET_OS").as_deref(), Ok("windows")) {
let cuda_home = env::var("CUDA_HOME")
.or_else(|_| env::var("CUDA_PATH"))
.unwrap_or_else(|_| "/usr/local/cuda".to_string());
println!("cargo:rustc-link-search=native={cuda_home}/lib64/stubs");
}
println!("cargo:rustc-link-lib=dylib=cuda");
Ok(())
}
fn try_build_stringzillas_rocm(serial_flags: &HashMap<String, bool>) -> Result<(), cc::Error> {
let mut build = stringzillas_base_build(serial_flags);
build.cpp(true).define("SZ_USE_CUDA", "0").define("SZ_USE_ROCM", "1");
for flag in msvc_cxx_flags().iter().chain(no_builtin_flags()) {
build.flag(flag);
}
build.files(STRINGZILLAS_API_CU_SOURCES);
build.try_compile("stringzillas")
}
fn try_build_stringzillas_cpus(serial_flags: &HashMap<String, bool>) -> Result<(), cc::Error> {
let mut build = stringzillas_base_build(serial_flags);
build.cpp(true).define("SZ_USE_CUDA", "0").define("SZ_USE_ROCM", "0");
for flag in msvc_cxx_flags().iter().chain(no_builtin_flags()) {
build.flag(flag);
}
build.files(STRINGZILLAS_API_CPP_SOURCES);
build.files(STRINGZILLAS_CPUS_SOURCES);
build.try_compile("stringzillas")
}
fn build_stringzillas(serial_flags: &HashMap<String, bool>) {
println!("cargo:rerun-if-changed=c/stringzillas");
println!("cargo:rerun-if-changed=include/stringzillas");
let is_cuda = env::var("CARGO_FEATURE_CUDA").is_ok();
let is_rocm = env::var("CARGO_FEATURE_ROCM").is_ok();
let gpu_ok = if is_cuda {
try_build_stringzillas_cuda(serial_flags).is_ok()
} else if is_rocm {
try_build_stringzillas_rocm(serial_flags).is_ok()
} else {
false
};
if gpu_ok {
return;
}
if is_cuda || is_rocm {
println!("cargo:warning=GPU backend unavailable; building CPU-only StringZillas instead");
}
try_build_stringzillas_cpus(serial_flags).expect("failed to compile CPU-only StringZillas");
}