mirror of
https://github.com/BLAKE3-team/BLAKE3
synced 2025-01-21 15:50:01 +01:00
324 lines
10 KiB
Rust
324 lines
10 KiB
Rust
use std::env;
|
|
|
|
fn defined(var: &str) -> bool {
|
|
println!("cargo:rerun-if-env-changed={}", var);
|
|
env::var_os(var).is_some()
|
|
}
|
|
|
|
fn is_pure() -> bool {
|
|
defined("CARGO_FEATURE_PURE")
|
|
}
|
|
|
|
fn should_prefer_intrinsics() -> bool {
|
|
defined("CARGO_FEATURE_PREFER_INTRINSICS")
|
|
}
|
|
|
|
fn is_neon() -> bool {
|
|
defined("CARGO_FEATURE_NEON")
|
|
}
|
|
|
|
fn is_no_neon() -> bool {
|
|
defined("CARGO_FEATURE_NO_NEON")
|
|
}
|
|
|
|
fn is_ci() -> bool {
|
|
defined("BLAKE3_CI")
|
|
}
|
|
|
|
fn warn(warning: &str) {
|
|
assert!(!warning.contains("\n"));
|
|
println!("cargo:warning={}", warning);
|
|
if is_ci() {
|
|
println!("cargo:warning=Warnings in CI are treated as errors. Build failed.");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn target_components() -> Vec<String> {
|
|
let target = env::var("TARGET").unwrap();
|
|
target.split("-").map(|s| s.to_string()).collect()
|
|
}
|
|
|
|
fn is_x86_64() -> bool {
|
|
target_components()[0] == "x86_64"
|
|
}
|
|
|
|
fn is_x86_32() -> bool {
|
|
let arch = &target_components()[0];
|
|
arch == "i386" || arch == "i586" || arch == "i686"
|
|
}
|
|
|
|
fn is_arm() -> bool {
|
|
is_armv7() || is_aarch64() || target_components()[0] == "arm"
|
|
}
|
|
|
|
fn is_aarch64() -> bool {
|
|
target_components()[0] == "aarch64"
|
|
}
|
|
|
|
fn is_armv7() -> bool {
|
|
target_components()[0] == "armv7"
|
|
}
|
|
|
|
fn endianness() -> String {
|
|
let endianness = env::var("CARGO_CFG_TARGET_ENDIAN").unwrap();
|
|
assert!(endianness == "little" || endianness == "big");
|
|
endianness
|
|
}
|
|
|
|
fn is_little_endian() -> bool {
|
|
endianness() == "little"
|
|
}
|
|
|
|
fn is_big_endian() -> bool {
|
|
endianness() == "big"
|
|
}
|
|
|
|
// Windows targets may be using the MSVC toolchain or the MinGW toolchain. The
|
|
// right compiler flags to use depend on the toolchain. (And we don't want to
|
|
// use flag_if_supported, because we don't want features to be silently
|
|
// disabled by old compilers.)
|
|
fn is_windows_msvc() -> bool {
|
|
// Some targets are only two components long, so check in steps.
|
|
target_components()[1] == "pc"
|
|
&& target_components()[2] == "windows"
|
|
&& target_components()[3] == "msvc"
|
|
}
|
|
|
|
// MinGW toolchain uses 2 different targets depending on the main compiler.
|
|
// Target for a general MinGW toolchain ends with `-gnu` (GCC is used as C
|
|
// compiler). Target for a LLVM-MinGW toolchain (Clang is used as C compiler)
|
|
// ends with `-gnullvm`.
|
|
fn is_windows_gnu() -> bool {
|
|
// Some targets are only two components long, so check in steps.
|
|
target_components()[1] == "pc"
|
|
&& target_components()[2] == "windows"
|
|
&& target_components()[3] != "msvc"
|
|
}
|
|
|
|
fn new_build() -> cc::Build {
|
|
let mut build = cc::Build::new();
|
|
if !is_windows_msvc() {
|
|
build.flag("-std=c11");
|
|
}
|
|
// Do NOT trigger a rebuild any time the env changes (e.g. $PATH).
|
|
// This prevents all downstream crates from being rebuilt when `cargo check`
|
|
// or `cargo build` are run in different environments, like Rust Analyzer
|
|
// vs. in the terminal vs. in a Git pre-commit hook.
|
|
build.emit_rerun_if_env_changed(false);
|
|
build
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum CCompilerSupport {
|
|
NoCompiler,
|
|
NoAVX512,
|
|
YesAVX512,
|
|
}
|
|
use CCompilerSupport::*;
|
|
|
|
fn c_compiler_support() -> CCompilerSupport {
|
|
let build = new_build();
|
|
let flags_checked;
|
|
let support_result: Result<bool, _> = if is_windows_msvc() {
|
|
flags_checked = "/arch:AVX512";
|
|
build.is_flag_supported("/arch:AVX512")
|
|
} else {
|
|
// Check for both of the flags we use. If -mavx512f works, then -mavx512vl
|
|
// will probably always work too, but we might as well be thorough.
|
|
flags_checked = "-mavx512f and -mavx512vl";
|
|
match build.is_flag_supported("-mavx512f") {
|
|
Ok(true) => build.is_flag_supported("-mavx512vl"),
|
|
false_or_error => false_or_error,
|
|
}
|
|
};
|
|
match support_result {
|
|
Ok(true) => YesAVX512,
|
|
Ok(false) => {
|
|
warn(&format!(
|
|
"The C compiler {:?} does not support {}.",
|
|
build.get_compiler().path(),
|
|
flags_checked,
|
|
));
|
|
NoAVX512
|
|
}
|
|
Err(e) => {
|
|
println!("{:?}", e);
|
|
warn(&format!(
|
|
"No C compiler {:?} detected.",
|
|
build.get_compiler().path()
|
|
));
|
|
NoCompiler
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_sse2_sse41_avx2_rust_intrinsics() {
|
|
// No C code to compile here. Set the cfg flags that enable the Rust SSE2,
|
|
// SSE4.1, and AVX2 intrinsics modules. The regular Cargo build will compile
|
|
// them.
|
|
println!("cargo:rustc-cfg=blake3_sse2_rust");
|
|
println!("cargo:rustc-cfg=blake3_sse41_rust");
|
|
println!("cargo:rustc-cfg=blake3_avx2_rust");
|
|
}
|
|
|
|
fn build_sse2_sse41_avx2_assembly() {
|
|
// Build the assembly implementations for SSE4.1 and AVX2. This is
|
|
// preferred, but it only supports x86_64.
|
|
assert!(is_x86_64());
|
|
println!("cargo:rustc-cfg=blake3_sse2_ffi");
|
|
println!("cargo:rustc-cfg=blake3_sse41_ffi");
|
|
println!("cargo:rustc-cfg=blake3_avx2_ffi");
|
|
let mut build = new_build();
|
|
if is_windows_msvc() {
|
|
build.file("c/blake3_sse2_x86-64_windows_msvc.asm");
|
|
build.file("c/blake3_sse41_x86-64_windows_msvc.asm");
|
|
build.file("c/blake3_avx2_x86-64_windows_msvc.asm");
|
|
} else if is_windows_gnu() {
|
|
build.file("c/blake3_sse2_x86-64_windows_gnu.S");
|
|
build.file("c/blake3_sse41_x86-64_windows_gnu.S");
|
|
build.file("c/blake3_avx2_x86-64_windows_gnu.S");
|
|
} else {
|
|
// All non-Windows implementations are assumed to support
|
|
// Linux-style assembly. These files do contain a small
|
|
// explicit workaround for macOS also.
|
|
build.file("c/blake3_sse2_x86-64_unix.S");
|
|
build.file("c/blake3_sse41_x86-64_unix.S");
|
|
build.file("c/blake3_avx2_x86-64_unix.S");
|
|
}
|
|
build.compile("blake3_sse2_sse41_avx2_assembly");
|
|
}
|
|
|
|
fn build_avx512_c_intrinsics() {
|
|
// This is required on 32-bit x86 targets, since the assembly
|
|
// implementation doesn't support those.
|
|
println!("cargo:rustc-cfg=blake3_avx512_ffi");
|
|
let mut build = new_build();
|
|
build.file("c/blake3_avx512.c");
|
|
if is_windows_msvc() {
|
|
build.flag("/arch:AVX512");
|
|
} else {
|
|
build.flag("-mavx512f");
|
|
build.flag("-mavx512vl");
|
|
}
|
|
if is_windows_gnu() {
|
|
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65782.
|
|
build.flag("-fno-asynchronous-unwind-tables");
|
|
}
|
|
build.compile("blake3_avx512_intrinsics");
|
|
}
|
|
|
|
fn build_avx512_assembly() {
|
|
// Build the assembly implementation for AVX-512. This is preferred, but it
|
|
// only supports x86_64.
|
|
assert!(is_x86_64());
|
|
println!("cargo:rustc-cfg=blake3_avx512_ffi");
|
|
let mut build = new_build();
|
|
if is_windows_msvc() {
|
|
build.file("c/blake3_avx512_x86-64_windows_msvc.asm");
|
|
} else {
|
|
if is_windows_gnu() {
|
|
build.file("c/blake3_avx512_x86-64_windows_gnu.S");
|
|
} else {
|
|
// All non-Windows implementations are assumed to support Linux-style
|
|
// assembly. These files do contain a small explicit workaround for
|
|
// macOS also.
|
|
build.file("c/blake3_avx512_x86-64_unix.S");
|
|
}
|
|
// Older versions of Clang require these flags, even for assembly. See
|
|
// https://github.com/BLAKE3-team/BLAKE3/issues/79.
|
|
build.flag("-mavx512f");
|
|
build.flag("-mavx512vl");
|
|
}
|
|
build.compile("blake3_avx512_assembly");
|
|
}
|
|
|
|
fn build_neon_c_intrinsics() {
|
|
let mut build = new_build();
|
|
// Note that blake3_neon.c normally depends on the blake3_portable.c
|
|
// for the single-instance compression function, but we expose
|
|
// portable.rs over FFI instead. See ffi_neon.rs.
|
|
build.file("c/blake3_neon.c");
|
|
// ARMv7 platforms that support NEON generally need the following
|
|
// flags. AArch64 supports NEON by default and does not support -mpfu.
|
|
if is_armv7() {
|
|
build.flag("-mfpu=neon-vfpv4");
|
|
build.flag("-mfloat-abi=hard");
|
|
}
|
|
build.compile("blake3_neon");
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// As of Rust 1.80, unrecognized config names are warnings. Give Cargo all of our config names.
|
|
let all_cfgs = [
|
|
"blake3_sse2_ffi",
|
|
"blake3_sse2_rust",
|
|
"blake3_sse41_ffi",
|
|
"blake3_sse41_rust",
|
|
"blake3_avx2_ffi",
|
|
"blake3_avx2_rust",
|
|
"blake3_avx512_ffi",
|
|
"blake3_neon",
|
|
];
|
|
for cfg_name in all_cfgs {
|
|
// TODO: Switch this whole file to the new :: syntax when our MSRV reaches 1.77.
|
|
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
|
|
println!("cargo:rustc-check-cfg=cfg({cfg_name}, values(none()))");
|
|
}
|
|
|
|
if is_pure() && is_neon() {
|
|
panic!("It doesn't make sense to enable both \"pure\" and \"neon\".");
|
|
}
|
|
|
|
if is_no_neon() && is_neon() {
|
|
panic!("It doesn't make sense to enable both \"no_neon\" and \"neon\".");
|
|
}
|
|
|
|
if is_x86_64() || is_x86_32() {
|
|
let support = c_compiler_support();
|
|
if is_x86_32() || should_prefer_intrinsics() || is_pure() || support == NoCompiler {
|
|
build_sse2_sse41_avx2_rust_intrinsics();
|
|
} else {
|
|
// We assume that all C compilers can assemble SSE4.1 and AVX2. We
|
|
// don't explicitly check for support.
|
|
build_sse2_sse41_avx2_assembly();
|
|
}
|
|
|
|
if is_pure() || support == NoCompiler || support == NoAVX512 {
|
|
// The binary will not include any AVX-512 code.
|
|
} else if is_x86_32() || should_prefer_intrinsics() {
|
|
build_avx512_c_intrinsics();
|
|
} else {
|
|
build_avx512_assembly();
|
|
}
|
|
}
|
|
|
|
if is_neon() && is_big_endian() {
|
|
panic!("The NEON implementation doesn't support big-endian ARM.")
|
|
}
|
|
|
|
if (is_arm() && is_neon())
|
|
|| (!is_no_neon() && !is_pure() && is_aarch64() && is_little_endian())
|
|
{
|
|
println!("cargo:rustc-cfg=blake3_neon");
|
|
build_neon_c_intrinsics();
|
|
}
|
|
|
|
// The `cc` crate doesn't automatically emit rerun-if directives for the
|
|
// environment variables it supports, in particular for $CC. We expect to
|
|
// do a lot of benchmarking across different compilers, so we explicitly
|
|
// add the variables that we're likely to need.
|
|
println!("cargo:rerun-if-env-changed=CC");
|
|
println!("cargo:rerun-if-env-changed=CFLAGS");
|
|
|
|
// Ditto for source files, though these shouldn't change as often.
|
|
for file in std::fs::read_dir("c")? {
|
|
println!(
|
|
"cargo:rerun-if-changed={}",
|
|
file?.path().to_str().expect("utf-8")
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|