summaryrefslogtreecommitdiff
path: root/build.rs
blob: ac1d6a648ad0cddca3a9d3789a7bddf510e225b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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"
}

// Windows targets may be using the MSVC toolchain or the GNU 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"
}

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] == "gnu"
}

fn new_build() -> cc::Build {
    let mut build = cc::Build::new();
    if !is_windows_msvc() {
        build.flag("-std=c11");
    }
    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>> {
    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_arm() && is_neon()) || (!is_no_neon() && !is_pure() && is_aarch64()) {
        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(())
}