bootstrap/core/build_steps/
llvm.rs

1//! Compilation of native dependencies like LLVM.
2//!
3//! Native projects like LLVM unfortunately aren't suited just yet for
4//! compilation in build scripts that Cargo has. This is because the
5//! compilation takes a *very* long time but also because we don't want to
6//! compile LLVM 3 times as part of a normal bootstrap (we want it cached).
7//!
8//! LLVM and compiler-rt are essentially just wired up to everything else to
9//! ensure that they're always in place if needed.
10
11use std::env::consts::EXE_EXTENSION;
12use std::ffi::{OsStr, OsString};
13use std::path::{Path, PathBuf};
14use std::sync::OnceLock;
15use std::{env, fs};
16
17use build_helper::git::PathFreshness;
18
19use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata};
20use crate::core::config::{Config, TargetSelection};
21use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash};
22use crate::utils::exec::command;
23use crate::utils::helpers::{
24    self, exe, get_clang_cl_resource_dir, t, unhashed_basename, up_to_date,
25};
26use crate::{CLang, GitRepo, Kind, trace};
27
28#[derive(Clone)]
29pub struct LlvmResult {
30    /// Path to llvm-config binary.
31    /// NB: This is always the host llvm-config!
32    pub host_llvm_config: PathBuf,
33    /// Path to LLVM cmake directory for the target.
34    pub llvm_cmake_dir: PathBuf,
35}
36
37pub struct Meta {
38    stamp: BuildStamp,
39    res: LlvmResult,
40    out_dir: PathBuf,
41    root: String,
42}
43
44pub enum LlvmBuildStatus {
45    AlreadyBuilt(LlvmResult),
46    ShouldBuild(Meta),
47}
48
49impl LlvmBuildStatus {
50    pub fn should_build(&self) -> bool {
51        match self {
52            LlvmBuildStatus::AlreadyBuilt(_) => false,
53            LlvmBuildStatus::ShouldBuild(_) => true,
54        }
55    }
56
57    #[cfg(test)]
58    pub fn llvm_result(&self) -> &LlvmResult {
59        match self {
60            LlvmBuildStatus::AlreadyBuilt(res) => res,
61            LlvmBuildStatus::ShouldBuild(meta) => &meta.res,
62        }
63    }
64}
65
66/// Linker flags to pass to LLVM's CMake invocation.
67#[derive(Debug, Clone, Default)]
68struct LdFlags {
69    /// CMAKE_EXE_LINKER_FLAGS
70    exe: OsString,
71    /// CMAKE_SHARED_LINKER_FLAGS
72    shared: OsString,
73    /// CMAKE_MODULE_LINKER_FLAGS
74    module: OsString,
75}
76
77impl LdFlags {
78    fn push_all(&mut self, s: impl AsRef<OsStr>) {
79        let s = s.as_ref();
80        self.exe.push(" ");
81        self.exe.push(s);
82        self.shared.push(" ");
83        self.shared.push(s);
84        self.module.push(" ");
85        self.module.push(s);
86    }
87}
88
89/// This returns whether we've already previously built LLVM.
90///
91/// It's used to avoid busting caches during x.py check -- if we've already built
92/// LLVM, it's fine for us to not try to avoid doing so.
93///
94/// This will return the llvm-config if it can get it (but it will not build it
95/// if not).
96pub fn prebuilt_llvm_config(
97    builder: &Builder<'_>,
98    target: TargetSelection,
99    // Certain commands (like `x test mir-opt --bless`) may call this function with different targets,
100    // which could bypass the CI LLVM early-return even if `builder.config.llvm_from_ci` is true.
101    // This flag should be `true` only if the caller needs the LLVM sources (e.g., if it will build LLVM).
102    handle_submodule_when_needed: bool,
103) -> LlvmBuildStatus {
104    builder.config.maybe_download_ci_llvm();
105
106    // If we're using a custom LLVM bail out here, but we can only use a
107    // custom LLVM for the build triple.
108    if let Some(config) = builder.config.target_config.get(&target)
109        && let Some(ref s) = config.llvm_config
110    {
111        check_llvm_version(builder, s);
112        let host_llvm_config = s.to_path_buf();
113        let mut llvm_cmake_dir = host_llvm_config.clone();
114        llvm_cmake_dir.pop();
115        llvm_cmake_dir.pop();
116        llvm_cmake_dir.push("lib");
117        llvm_cmake_dir.push("cmake");
118        llvm_cmake_dir.push("llvm");
119        return LlvmBuildStatus::AlreadyBuilt(LlvmResult { host_llvm_config, llvm_cmake_dir });
120    }
121
122    if handle_submodule_when_needed {
123        // If submodules are disabled, this does nothing.
124        builder.config.update_submodule("src/llvm-project");
125    }
126
127    let root = "src/llvm-project/llvm";
128    let out_dir = builder.llvm_out(target);
129
130    let build_llvm_config = if let Some(build_llvm_config) = builder
131        .config
132        .target_config
133        .get(&builder.config.host_target)
134        .and_then(|config| config.llvm_config.clone())
135    {
136        build_llvm_config
137    } else {
138        let mut llvm_config_ret_dir = builder.llvm_out(builder.config.host_target);
139        llvm_config_ret_dir.push("bin");
140        llvm_config_ret_dir.join(exe("llvm-config", builder.config.host_target))
141    };
142
143    let llvm_cmake_dir = out_dir.join("lib/cmake/llvm");
144    let res = LlvmResult { host_llvm_config: build_llvm_config, llvm_cmake_dir };
145
146    static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
147    let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
148        generate_smart_stamp_hash(
149            builder,
150            &builder.config.src.join("src/llvm-project"),
151            builder.in_tree_llvm_info.sha().unwrap_or_default(),
152        )
153    });
154
155    let stamp = BuildStamp::new(&out_dir).with_prefix("llvm").add_stamp(smart_stamp_hash);
156
157    if stamp.is_up_to_date() {
158        if stamp.stamp().is_empty() {
159            builder.info(
160                "Could not determine the LLVM submodule commit hash. \
161                     Assuming that an LLVM rebuild is not necessary.",
162            );
163            builder.info(&format!(
164                "To force LLVM to rebuild, remove the file `{}`",
165                stamp.path().display()
166            ));
167        }
168        return LlvmBuildStatus::AlreadyBuilt(res);
169    }
170
171    LlvmBuildStatus::ShouldBuild(Meta { stamp, res, out_dir, root: root.into() })
172}
173
174/// Paths whose changes invalidate LLVM downloads.
175pub const LLVM_INVALIDATION_PATHS: &[&str] = &[
176    "src/llvm-project",
177    "src/bootstrap/download-ci-llvm-stamp",
178    // the LLVM shared object file is named `LLVM-<LLVM-version>-rust-{version}-nightly`
179    "src/version",
180];
181
182/// Detect whether LLVM sources have been modified locally or not.
183pub(crate) fn detect_llvm_freshness(config: &Config, is_git: bool) -> PathFreshness {
184    if is_git {
185        config.check_path_modifications(LLVM_INVALIDATION_PATHS)
186    } else if let Some(info) = crate::utils::channel::read_commit_info_file(&config.src) {
187        PathFreshness::LastModifiedUpstream { upstream: info.sha.trim().to_owned() }
188    } else {
189        PathFreshness::MissingUpstream
190    }
191}
192
193/// Returns whether the CI-found LLVM is currently usable.
194///
195/// This checks the build triple platform to confirm we're usable at all, and if LLVM
196/// with/without assertions is available.
197pub(crate) fn is_ci_llvm_available_for_target(
198    host_target: &TargetSelection,
199    asserts: bool,
200) -> bool {
201    // This is currently all tier 1 targets and tier 2 targets with host tools
202    // (since others may not have CI artifacts)
203    // https://doc.rust-lang.org/rustc/platform-support.html#tier-1
204    let supported_platforms = [
205        // tier 1
206        ("aarch64-unknown-linux-gnu", false),
207        ("aarch64-apple-darwin", false),
208        ("aarch64-pc-windows-msvc", false),
209        ("i686-pc-windows-gnu", false),
210        ("i686-pc-windows-msvc", false),
211        ("i686-unknown-linux-gnu", false),
212        ("x86_64-unknown-linux-gnu", true),
213        ("x86_64-apple-darwin", true),
214        ("x86_64-pc-windows-gnu", true),
215        ("x86_64-pc-windows-msvc", true),
216        // tier 2 with host tools
217        ("aarch64-unknown-linux-musl", false),
218        ("arm-unknown-linux-gnueabi", false),
219        ("arm-unknown-linux-gnueabihf", false),
220        ("armv7-unknown-linux-gnueabihf", false),
221        ("loongarch64-unknown-linux-gnu", false),
222        ("loongarch64-unknown-linux-musl", false),
223        ("powerpc-unknown-linux-gnu", false),
224        ("powerpc64-unknown-linux-gnu", false),
225        ("powerpc64le-unknown-linux-gnu", false),
226        ("powerpc64le-unknown-linux-musl", false),
227        ("riscv64gc-unknown-linux-gnu", false),
228        ("s390x-unknown-linux-gnu", false),
229        ("x86_64-unknown-freebsd", false),
230        ("x86_64-unknown-illumos", false),
231        ("x86_64-unknown-linux-musl", false),
232        ("x86_64-unknown-netbsd", false),
233    ];
234
235    if !supported_platforms.contains(&(&*host_target.triple, asserts))
236        && (asserts || !supported_platforms.contains(&(&*host_target.triple, true)))
237    {
238        return false;
239    }
240
241    true
242}
243
244#[derive(Debug, Clone, Hash, PartialEq, Eq)]
245pub struct Llvm {
246    pub target: TargetSelection,
247}
248
249impl Step for Llvm {
250    type Output = LlvmResult;
251
252    const IS_HOST: bool = true;
253
254    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
255        run.path("src/llvm-project").path("src/llvm-project/llvm")
256    }
257
258    fn make_run(run: RunConfig<'_>) {
259        run.builder.ensure(Llvm { target: run.target });
260    }
261
262    /// Compile LLVM for `target`.
263    fn run(self, builder: &Builder<'_>) -> LlvmResult {
264        let target = self.target;
265        let target_native = if self.target.starts_with("riscv") {
266            // RISC-V target triples in Rust is not named the same as C compiler target triples.
267            // This converts Rust RISC-V target triples to C compiler triples.
268            let idx = target.triple.find('-').unwrap();
269
270            format!("riscv{}{}", &target.triple[5..7], &target.triple[idx..])
271        } else if self.target.starts_with("powerpc") && self.target.ends_with("freebsd") {
272            // FreeBSD 13 had incompatible ABI changes on all PowerPC platforms.
273            // Set the version suffix to 13.0 so the correct target details are used.
274            format!("{}{}", self.target, "13.0")
275        } else {
276            target.to_string()
277        };
278
279        // If LLVM has already been built or been downloaded through download-ci-llvm, we avoid building it again.
280        let Meta { stamp, res, out_dir, root } = match prebuilt_llvm_config(builder, target, true) {
281            LlvmBuildStatus::AlreadyBuilt(p) => return p,
282            LlvmBuildStatus::ShouldBuild(m) => m,
283        };
284
285        if builder.llvm_link_shared() && target.is_windows() && !target.ends_with("windows-gnullvm")
286        {
287            panic!("shared linking to LLVM is not currently supported on {}", target.triple);
288        }
289
290        let _guard = builder.msg_unstaged(Kind::Build, "LLVM", target);
291        t!(stamp.remove());
292        let _time = helpers::timeit(builder);
293        t!(fs::create_dir_all(&out_dir));
294
295        // https://llvm.org/docs/CMake.html
296        let mut cfg = cmake::Config::new(builder.src.join(root));
297        let mut ldflags = LdFlags::default();
298
299        let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
300            (false, _) => "Debug",
301            (true, false) => "Release",
302            (true, true) => "RelWithDebInfo",
303        };
304
305        // NOTE: remember to also update `bootstrap.example.toml` when changing the
306        // defaults!
307        let llvm_targets = match &builder.config.llvm_targets {
308            Some(s) => s,
309            None => {
310                "AArch64;AMDGPU;ARM;BPF;Hexagon;LoongArch;MSP430;Mips;NVPTX;PowerPC;RISCV;\
311                     Sparc;SystemZ;WebAssembly;X86"
312            }
313        };
314
315        let llvm_exp_targets = match builder.config.llvm_experimental_targets {
316            Some(ref s) => s,
317            None => "AVR;M68k;CSKY;Xtensa",
318        };
319
320        let assertions = if builder.config.llvm_assertions { "ON" } else { "OFF" };
321        let plugins = if builder.config.llvm_plugins { "ON" } else { "OFF" };
322        let enable_tests = if builder.config.llvm_tests { "ON" } else { "OFF" };
323        let enable_warnings = if builder.config.llvm_enable_warnings { "ON" } else { "OFF" };
324
325        cfg.out_dir(&out_dir)
326            .profile(profile)
327            .define("LLVM_ENABLE_ASSERTIONS", assertions)
328            .define("LLVM_UNREACHABLE_OPTIMIZE", "OFF")
329            .define("LLVM_ENABLE_PLUGINS", plugins)
330            .define("LLVM_TARGETS_TO_BUILD", llvm_targets)
331            .define("LLVM_EXPERIMENTAL_TARGETS_TO_BUILD", llvm_exp_targets)
332            .define("LLVM_INCLUDE_EXAMPLES", "OFF")
333            .define("LLVM_INCLUDE_DOCS", "OFF")
334            .define("LLVM_INCLUDE_BENCHMARKS", "OFF")
335            .define("LLVM_INCLUDE_TESTS", enable_tests)
336            .define("LLVM_ENABLE_LIBEDIT", "OFF")
337            .define("LLVM_ENABLE_BINDINGS", "OFF")
338            .define("LLVM_ENABLE_Z3_SOLVER", "OFF")
339            .define("LLVM_PARALLEL_COMPILE_JOBS", builder.jobs().to_string())
340            .define("LLVM_TARGET_ARCH", target_native.split('-').next().unwrap())
341            .define("LLVM_DEFAULT_TARGET_TRIPLE", target_native)
342            .define("LLVM_ENABLE_WARNINGS", enable_warnings);
343
344        // Parts of our test suite rely on the `FileCheck` tool, which is built by default in
345        // `build/$TARGET/llvm/build/bin` is but *not* then installed to `build/$TARGET/llvm/bin`.
346        // This flag makes sure `FileCheck` is copied in the final binaries directory.
347        cfg.define("LLVM_INSTALL_UTILS", "ON");
348
349        if builder.config.llvm_profile_generate {
350            cfg.define("LLVM_BUILD_INSTRUMENTED", "IR");
351            if let Ok(llvm_profile_dir) = std::env::var("LLVM_PROFILE_DIR") {
352                cfg.define("LLVM_PROFILE_DATA_DIR", llvm_profile_dir);
353            }
354            cfg.define("LLVM_BUILD_RUNTIME", "No");
355        }
356        if let Some(path) = builder.config.llvm_profile_use.as_ref() {
357            cfg.define("LLVM_PROFDATA_FILE", path);
358        }
359
360        // Libraries for ELF section compression and profraw files merging.
361        if !target.is_msvc() {
362            cfg.define("LLVM_ENABLE_ZLIB", "ON");
363        } else {
364            cfg.define("LLVM_ENABLE_ZLIB", "OFF");
365        }
366
367        // Are we compiling for iOS/tvOS/watchOS/visionOS?
368        if target.contains("apple-ios")
369            || target.contains("apple-tvos")
370            || target.contains("apple-watchos")
371            || target.contains("apple-visionos")
372        {
373            // Prevent cmake from adding -bundle to CFLAGS automatically, which leads to a compiler error because "-bitcode_bundle" also gets added.
374            cfg.define("LLVM_ENABLE_PLUGINS", "OFF");
375            // Zlib fails to link properly, leading to a compiler error.
376            cfg.define("LLVM_ENABLE_ZLIB", "OFF");
377        }
378
379        // This setting makes the LLVM tools link to the dynamic LLVM library,
380        // which saves both memory during parallel links and overall disk space
381        // for the tools. We don't do this on every platform as it doesn't work
382        // equally well everywhere.
383        if builder.llvm_link_shared() {
384            cfg.define("LLVM_LINK_LLVM_DYLIB", "ON");
385        }
386
387        if (target.starts_with("csky")
388            || target.starts_with("riscv")
389            || target.starts_with("sparc-"))
390            && !target.contains("freebsd")
391            && !target.contains("openbsd")
392            && !target.contains("netbsd")
393        {
394            // CSKY and RISC-V GCC erroneously requires linking against
395            // `libatomic` when using 1-byte and 2-byte C++
396            // atomics but the LLVM build system check cannot
397            // detect this. Therefore it is set manually here.
398            // Some BSD uses Clang as its system compiler and
399            // provides no libatomic in its base system so does
400            // not want this. 32-bit SPARC requires linking against
401            // libatomic as well.
402            ldflags.exe.push(" -latomic");
403            ldflags.shared.push(" -latomic");
404        }
405
406        if target.starts_with("mips") && target.contains("netbsd") {
407            // LLVM wants 64-bit atomics, while mipsel is 32-bit only, so needs -latomic
408            ldflags.exe.push(" -latomic");
409            ldflags.shared.push(" -latomic");
410        }
411
412        if target.starts_with("arm64ec") {
413            // MSVC linker requires the -machine:arm64ec flag to be passed to
414            // know it's linking as Arm64EC (vs Arm64X).
415            ldflags.exe.push(" -machine:arm64ec");
416            ldflags.shared.push(" -machine:arm64ec");
417        }
418
419        if target.is_msvc() {
420            cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
421            cfg.static_crt(true);
422        }
423
424        if target.starts_with("i686") {
425            cfg.define("LLVM_BUILD_32_BITS", "ON");
426        }
427
428        if target.starts_with("x86_64") && target.contains("ohos") {
429            cfg.define("LLVM_TOOL_LLVM_RTDYLD_BUILD", "OFF");
430        }
431
432        let mut enabled_llvm_projects = Vec::new();
433
434        if helpers::forcing_clang_based_tests() {
435            enabled_llvm_projects.push("clang");
436        }
437
438        if builder.config.llvm_polly {
439            enabled_llvm_projects.push("polly");
440        }
441
442        if builder.config.llvm_clang {
443            enabled_llvm_projects.push("clang");
444        }
445
446        // We want libxml to be disabled.
447        // See https://github.com/rust-lang/rust/pull/50104
448        cfg.define("LLVM_ENABLE_LIBXML2", "OFF");
449
450        let mut enabled_llvm_runtimes = Vec::new();
451
452        if helpers::forcing_clang_based_tests() {
453            enabled_llvm_runtimes.push("compiler-rt");
454        }
455
456        // This is an experimental flag, which likely builds more than necessary.
457        // We will optimize it when we get closer to releasing it on nightly.
458        if builder.config.llvm_offload {
459            enabled_llvm_runtimes.push("offload");
460            //FIXME(ZuseZ4): LLVM intends to drop the offload dependency on openmp.
461            //Remove this line once they achieved it.
462            enabled_llvm_runtimes.push("openmp");
463            enabled_llvm_projects.push("compiler-rt");
464        }
465
466        if !enabled_llvm_projects.is_empty() {
467            enabled_llvm_projects.sort();
468            enabled_llvm_projects.dedup();
469            cfg.define("LLVM_ENABLE_PROJECTS", enabled_llvm_projects.join(";"));
470        }
471
472        if !enabled_llvm_runtimes.is_empty() {
473            enabled_llvm_runtimes.sort();
474            enabled_llvm_runtimes.dedup();
475            cfg.define("LLVM_ENABLE_RUNTIMES", enabled_llvm_runtimes.join(";"));
476        }
477
478        if let Some(num_linkers) = builder.config.llvm_link_jobs
479            && num_linkers > 0
480        {
481            cfg.define("LLVM_PARALLEL_LINK_JOBS", num_linkers.to_string());
482        }
483
484        // https://llvm.org/docs/HowToCrossCompileLLVM.html
485        if !builder.config.is_host_target(target) {
486            let LlvmResult { host_llvm_config, .. } =
487                builder.ensure(Llvm { target: builder.config.host_target });
488            if !builder.config.dry_run() {
489                let llvm_bindir = command(&host_llvm_config)
490                    .arg("--bindir")
491                    .cached()
492                    .run_capture_stdout(builder)
493                    .stdout();
494                let host_bin = Path::new(llvm_bindir.trim());
495                cfg.define(
496                    "LLVM_TABLEGEN",
497                    host_bin.join("llvm-tblgen").with_extension(EXE_EXTENSION),
498                );
499                // LLVM_NM is required for cross compiling using MSVC
500                cfg.define("LLVM_NM", host_bin.join("llvm-nm").with_extension(EXE_EXTENSION));
501            }
502            cfg.define("LLVM_CONFIG_PATH", host_llvm_config);
503            if builder.config.llvm_clang {
504                let build_bin =
505                    builder.llvm_out(builder.config.host_target).join("build").join("bin");
506                let clang_tblgen = build_bin.join("clang-tblgen").with_extension(EXE_EXTENSION);
507                if !builder.config.dry_run() && !clang_tblgen.exists() {
508                    panic!("unable to find {}", clang_tblgen.display());
509                }
510                cfg.define("CLANG_TABLEGEN", clang_tblgen);
511            }
512        }
513
514        let llvm_version_suffix = if let Some(ref suffix) = builder.config.llvm_version_suffix {
515            // Allow version-suffix="" to not define a version suffix at all.
516            if !suffix.is_empty() { Some(suffix.to_string()) } else { None }
517        } else if builder.config.channel == "dev" {
518            // Changes to a version suffix require a complete rebuild of the LLVM.
519            // To avoid rebuilds during a time of version bump, don't include rustc
520            // release number on the dev channel.
521            Some("-rust-dev".to_string())
522        } else {
523            Some(format!("-rust-{}-{}", builder.version, builder.config.channel))
524        };
525        if let Some(ref suffix) = llvm_version_suffix {
526            cfg.define("LLVM_VERSION_SUFFIX", suffix);
527        }
528
529        configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
530        configure_llvm(builder, target, &mut cfg);
531
532        for (key, val) in &builder.config.llvm_build_config {
533            cfg.define(key, val);
534        }
535
536        if builder.config.dry_run() {
537            return res;
538        }
539
540        cfg.build();
541
542        // Helper to find the name of LLVM's shared library on darwin and linux.
543        let find_llvm_lib_name = |extension| {
544            let major = get_llvm_version_major(builder, &res.host_llvm_config);
545            match &llvm_version_suffix {
546                Some(version_suffix) => format!("libLLVM-{major}{version_suffix}.{extension}"),
547                None => format!("libLLVM-{major}.{extension}"),
548            }
549        };
550
551        // FIXME(ZuseZ4): Do we need that for Enzyme too?
552        // When building LLVM with LLVM_LINK_LLVM_DYLIB for macOS, an unversioned
553        // libLLVM.dylib will be built. However, llvm-config will still look
554        // for a versioned path like libLLVM-14.dylib. Manually create a symbolic
555        // link to make llvm-config happy.
556        if builder.llvm_link_shared() && target.contains("apple-darwin") {
557            let lib_name = find_llvm_lib_name("dylib");
558            let lib_llvm = out_dir.join("build").join("lib").join(lib_name);
559            if !lib_llvm.exists() {
560                t!(builder.symlink_file("libLLVM.dylib", &lib_llvm));
561            }
562        }
563
564        // When building LLVM as a shared library on linux, it can contain unexpected debuginfo:
565        // some can come from the C++ standard library. Unless we're explicitly requesting LLVM to
566        // be built with debuginfo, strip it away after the fact, to make dist artifacts smaller.
567        if builder.llvm_link_shared()
568            && builder.config.llvm_optimize
569            && !builder.config.llvm_release_debuginfo
570        {
571            // Find the name of the LLVM shared library that we just built.
572            let lib_name = find_llvm_lib_name("so");
573
574            // If the shared library exists in LLVM's `/build/lib/` or `/lib/` folders, strip its
575            // debuginfo.
576            crate::core::build_steps::compile::strip_debug(
577                builder,
578                target,
579                &out_dir.join("lib").join(&lib_name),
580            );
581            crate::core::build_steps::compile::strip_debug(
582                builder,
583                target,
584                &out_dir.join("build").join("lib").join(&lib_name),
585            );
586        }
587
588        t!(stamp.write());
589
590        res
591    }
592
593    fn metadata(&self) -> Option<StepMetadata> {
594        Some(StepMetadata::build("llvm", self.target))
595    }
596}
597
598pub fn get_llvm_version(builder: &Builder<'_>, llvm_config: &Path) -> String {
599    command(llvm_config)
600        .arg("--version")
601        .cached()
602        .run_capture_stdout(builder)
603        .stdout()
604        .trim()
605        .to_owned()
606}
607
608pub fn get_llvm_version_major(builder: &Builder<'_>, llvm_config: &Path) -> u8 {
609    let version = get_llvm_version(builder, llvm_config);
610    let major_str = version.split_once('.').expect("Failed to parse LLVM version").0;
611    major_str.parse().unwrap()
612}
613
614fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) {
615    if builder.config.dry_run() {
616        return;
617    }
618
619    let version = get_llvm_version(builder, llvm_config);
620    let mut parts = version.split('.').take(2).filter_map(|s| s.parse::<u32>().ok());
621    if let (Some(major), Some(_minor)) = (parts.next(), parts.next())
622        && major >= 20
623    {
624        return;
625    }
626    panic!("\n\nbad LLVM version: {version}, need >=20\n\n")
627}
628
629fn configure_cmake(
630    builder: &Builder<'_>,
631    target: TargetSelection,
632    cfg: &mut cmake::Config,
633    use_compiler_launcher: bool,
634    mut ldflags: LdFlags,
635    suppressed_compiler_flag_prefixes: &[&str],
636) {
637    // Do not print installation messages for up-to-date files.
638    // LLVM and LLD builds can produce a lot of those and hit CI limits on log size.
639    cfg.define("CMAKE_INSTALL_MESSAGE", "LAZY");
640
641    // Do not allow the user's value of DESTDIR to influence where
642    // LLVM will install itself. LLVM must always be installed in our
643    // own build directories.
644    cfg.env("DESTDIR", "");
645
646    if builder.ninja() {
647        cfg.generator("Ninja");
648    }
649    cfg.target(&target.triple).host(&builder.config.host_target.triple);
650
651    if !builder.config.is_host_target(target) {
652        cfg.define("CMAKE_CROSSCOMPILING", "True");
653
654        // NOTE: Ideally, we wouldn't have to do this, and `cmake-rs` would just handle it for us.
655        // But it currently determines this based on the `CARGO_CFG_TARGET_OS` environment variable,
656        // which isn't set when compiling outside `build.rs` (like bootstrap is).
657        //
658        // So for now, we define `CMAKE_SYSTEM_NAME` ourselves, to panicking in `cmake-rs`.
659        if target.contains("netbsd") {
660            cfg.define("CMAKE_SYSTEM_NAME", "NetBSD");
661        } else if target.contains("dragonfly") {
662            cfg.define("CMAKE_SYSTEM_NAME", "DragonFly");
663        } else if target.contains("openbsd") {
664            cfg.define("CMAKE_SYSTEM_NAME", "OpenBSD");
665        } else if target.contains("freebsd") {
666            cfg.define("CMAKE_SYSTEM_NAME", "FreeBSD");
667        } else if target.is_windows() {
668            cfg.define("CMAKE_SYSTEM_NAME", "Windows");
669        } else if target.contains("haiku") {
670            cfg.define("CMAKE_SYSTEM_NAME", "Haiku");
671        } else if target.contains("solaris") || target.contains("illumos") {
672            cfg.define("CMAKE_SYSTEM_NAME", "SunOS");
673        } else if target.contains("linux") {
674            cfg.define("CMAKE_SYSTEM_NAME", "Linux");
675        } else if target.contains("darwin") {
676            // macOS
677            cfg.define("CMAKE_SYSTEM_NAME", "Darwin");
678        } else if target.contains("ios") {
679            cfg.define("CMAKE_SYSTEM_NAME", "iOS");
680        } else if target.contains("tvos") {
681            cfg.define("CMAKE_SYSTEM_NAME", "tvOS");
682        } else if target.contains("visionos") {
683            cfg.define("CMAKE_SYSTEM_NAME", "visionOS");
684        } else if target.contains("watchos") {
685            cfg.define("CMAKE_SYSTEM_NAME", "watchOS");
686        } else if target.contains("none") {
687            // "none" should be the last branch
688            cfg.define("CMAKE_SYSTEM_NAME", "Generic");
689        } else {
690            builder.info(&format!(
691                "could not determine CMAKE_SYSTEM_NAME from the target `{target}`, build may fail",
692            ));
693            // Fallback, set `CMAKE_SYSTEM_NAME` anyhow to avoid the logic `cmake-rs` tries, and
694            // to avoid CMAKE_SYSTEM_NAME being inferred from the host.
695            cfg.define("CMAKE_SYSTEM_NAME", "Generic");
696        }
697
698        // When cross-compiling we should also set CMAKE_SYSTEM_VERSION, but in
699        // that case like CMake we cannot easily determine system version either.
700        //
701        // Since, the LLVM itself makes rather limited use of version checks in
702        // CMakeFiles (and then only in tests), and so far no issues have been
703        // reported, the system version is currently left unset.
704
705        if target.contains("apple") {
706            if !target.contains("darwin") {
707                // FIXME(madsmtm): compiler-rt's CMake setup is kinda weird, it seems like they do
708                // version testing etc. for macOS (i.e. Darwin), even while building for iOS?
709                //
710                // So for now we set it to "Darwin" on all Apple platforms.
711                cfg.define("CMAKE_SYSTEM_NAME", "Darwin");
712
713                // These two defines prevent CMake from automatically trying to add a MacOSX sysroot, which leads to a compiler error.
714                cfg.define("CMAKE_OSX_SYSROOT", "/");
715                cfg.define("CMAKE_OSX_DEPLOYMENT_TARGET", "");
716            }
717
718            // Make sure that CMake does not build universal binaries on macOS.
719            // Explicitly specify the one single target architecture.
720            if target.starts_with("aarch64") {
721                // macOS uses a different name for building arm64
722                cfg.define("CMAKE_OSX_ARCHITECTURES", "arm64");
723            } else if target.starts_with("i686") {
724                // macOS uses a different name for building i386
725                cfg.define("CMAKE_OSX_ARCHITECTURES", "i386");
726            } else {
727                cfg.define("CMAKE_OSX_ARCHITECTURES", target.triple.split('-').next().unwrap());
728            }
729        }
730    }
731
732    let sanitize_cc = |cc: &Path| {
733        if target.is_msvc() {
734            OsString::from(cc.to_str().unwrap().replace('\\', "/"))
735        } else {
736            cc.as_os_str().to_owned()
737        }
738    };
739
740    // MSVC with CMake uses msbuild by default which doesn't respect these
741    // vars that we'd otherwise configure. In that case we just skip this
742    // entirely.
743    if target.is_msvc() && !builder.ninja() {
744        return;
745    }
746
747    let (cc, cxx) = match builder.config.llvm_clang_cl {
748        Some(ref cl) => (cl.into(), cl.into()),
749        None => (builder.cc(target), builder.cxx(target).unwrap()),
750    };
751
752    // If ccache is configured we inform the build a little differently how
753    // to invoke ccache while also invoking our compilers.
754    if use_compiler_launcher && let Some(ref ccache) = builder.config.ccache {
755        cfg.define("CMAKE_C_COMPILER_LAUNCHER", ccache)
756            .define("CMAKE_CXX_COMPILER_LAUNCHER", ccache);
757    }
758    cfg.define("CMAKE_C_COMPILER", sanitize_cc(&cc))
759        .define("CMAKE_CXX_COMPILER", sanitize_cc(&cxx))
760        .define("CMAKE_ASM_COMPILER", sanitize_cc(&cc));
761
762    cfg.build_arg("-j").build_arg(builder.jobs().to_string());
763    // FIXME(madsmtm): Allow `cmake-rs` to select flags by itself by passing
764    // our flags via `.cflag`/`.cxxflag` instead.
765    //
766    // Needs `suppressed_compiler_flag_prefixes` to be gone, and hence
767    // https://github.com/llvm/llvm-project/issues/88780 to be fixed.
768    let mut cflags: OsString = builder
769        .cc_handled_clags(target, CLang::C)
770        .into_iter()
771        .chain(builder.cc_unhandled_cflags(target, GitRepo::Llvm, CLang::C))
772        .filter(|flag| {
773            !suppressed_compiler_flag_prefixes
774                .iter()
775                .any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
776        })
777        .collect::<Vec<String>>()
778        .join(" ")
779        .into();
780    if let Some(ref s) = builder.config.llvm_cflags {
781        cflags.push(" ");
782        cflags.push(s);
783    }
784    if target.contains("ohos") {
785        cflags.push(" -D_LINUX_SYSINFO_H");
786    }
787    if builder.config.llvm_clang_cl.is_some() {
788        cflags.push(format!(" --target={target}"));
789    }
790    cfg.define("CMAKE_C_FLAGS", cflags);
791    let mut cxxflags: OsString = builder
792        .cc_handled_clags(target, CLang::Cxx)
793        .into_iter()
794        .chain(builder.cc_unhandled_cflags(target, GitRepo::Llvm, CLang::Cxx))
795        .filter(|flag| {
796            !suppressed_compiler_flag_prefixes
797                .iter()
798                .any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
799        })
800        .collect::<Vec<String>>()
801        .join(" ")
802        .into();
803    if let Some(ref s) = builder.config.llvm_cxxflags {
804        cxxflags.push(" ");
805        cxxflags.push(s);
806    }
807    if target.contains("ohos") {
808        cxxflags.push(" -D_LINUX_SYSINFO_H");
809    }
810    if builder.config.llvm_clang_cl.is_some() {
811        cxxflags.push(format!(" --target={target}"));
812    }
813    cfg.define("CMAKE_CXX_FLAGS", cxxflags);
814    if let Some(ar) = builder.ar(target)
815        && ar.is_absolute()
816    {
817        // LLVM build breaks if `CMAKE_AR` is a relative path, for some reason it
818        // tries to resolve this path in the LLVM build directory.
819        cfg.define("CMAKE_AR", sanitize_cc(&ar));
820    }
821
822    if let Some(ranlib) = builder.ranlib(target)
823        && ranlib.is_absolute()
824    {
825        // LLVM build breaks if `CMAKE_RANLIB` is a relative path, for some reason it
826        // tries to resolve this path in the LLVM build directory.
827        cfg.define("CMAKE_RANLIB", sanitize_cc(&ranlib));
828    }
829
830    if let Some(ref flags) = builder.config.llvm_ldflags {
831        ldflags.push_all(flags);
832    }
833
834    if let Some(flags) = get_var("LDFLAGS", &builder.config.host_target.triple, &target.triple) {
835        ldflags.push_all(&flags);
836    }
837
838    // For distribution we want the LLVM tools to be *statically* linked to libstdc++.
839    // We also do this if the user explicitly requested static libstdc++.
840    if builder.config.llvm_static_stdcpp
841        && !target.is_msvc()
842        && !target.contains("netbsd")
843        && !target.contains("solaris")
844    {
845        if target.contains("apple") || target.is_windows() {
846            ldflags.push_all("-static-libstdc++");
847        } else {
848            ldflags.push_all("-Wl,-Bsymbolic -static-libstdc++");
849        }
850    }
851
852    cfg.define("CMAKE_SHARED_LINKER_FLAGS", &ldflags.shared);
853    cfg.define("CMAKE_MODULE_LINKER_FLAGS", &ldflags.module);
854    cfg.define("CMAKE_EXE_LINKER_FLAGS", &ldflags.exe);
855
856    if env::var_os("SCCACHE_ERROR_LOG").is_some() {
857        cfg.env("RUSTC_LOG", "sccache=warn");
858    }
859}
860
861fn configure_llvm(builder: &Builder<'_>, target: TargetSelection, cfg: &mut cmake::Config) {
862    // ThinLTO is only available when building with LLVM, enabling LLD is required.
863    // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin.
864    if builder.config.llvm_thin_lto {
865        cfg.define("LLVM_ENABLE_LTO", "Thin");
866        if !target.contains("apple") {
867            cfg.define("LLVM_ENABLE_LLD", "ON");
868        }
869    }
870
871    // Libraries for ELF section compression.
872    if builder.config.llvm_libzstd {
873        cfg.define("LLVM_ENABLE_ZSTD", "FORCE_ON");
874        cfg.define("LLVM_USE_STATIC_ZSTD", "TRUE");
875    } else {
876        cfg.define("LLVM_ENABLE_ZSTD", "OFF");
877    }
878
879    if let Some(ref linker) = builder.config.llvm_use_linker {
880        cfg.define("LLVM_USE_LINKER", linker);
881    }
882
883    if builder.config.llvm_allow_old_toolchain {
884        cfg.define("LLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN", "YES");
885    }
886}
887
888// Adapted from https://github.com/alexcrichton/cc-rs/blob/fba7feded71ee4f63cfe885673ead6d7b4f2f454/src/lib.rs#L2347-L2365
889fn get_var(var_base: &str, host: &str, target: &str) -> Option<OsString> {
890    let kind = if host == target { "HOST" } else { "TARGET" };
891    let target_u = target.replace('-', "_");
892    env::var_os(format!("{var_base}_{target}"))
893        .or_else(|| env::var_os(format!("{var_base}_{target_u}")))
894        .or_else(|| env::var_os(format!("{kind}_{var_base}")))
895        .or_else(|| env::var_os(var_base))
896}
897
898#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
899pub struct Enzyme {
900    pub target: TargetSelection,
901}
902
903impl Step for Enzyme {
904    type Output = PathBuf;
905    const IS_HOST: bool = true;
906
907    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
908        run.path("src/tools/enzyme/enzyme")
909    }
910
911    fn make_run(run: RunConfig<'_>) {
912        run.builder.ensure(Enzyme { target: run.target });
913    }
914
915    /// Compile Enzyme for `target`.
916    fn run(self, builder: &Builder<'_>) -> PathBuf {
917        builder.require_submodule(
918            "src/tools/enzyme",
919            Some("The Enzyme sources are required for autodiff."),
920        );
921        if builder.config.dry_run() {
922            let out_dir = builder.enzyme_out(self.target);
923            return out_dir;
924        }
925        let target = self.target;
926
927        let LlvmResult { host_llvm_config, .. } = builder.ensure(Llvm { target: self.target });
928
929        static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
930        let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
931            generate_smart_stamp_hash(
932                builder,
933                &builder.config.src.join("src/tools/enzyme"),
934                builder.enzyme_info.sha().unwrap_or_default(),
935            )
936        });
937
938        let out_dir = builder.enzyme_out(target);
939        let stamp = BuildStamp::new(&out_dir).with_prefix("enzyme").add_stamp(smart_stamp_hash);
940
941        trace!("checking build stamp to see if we need to rebuild enzyme artifacts");
942        if stamp.is_up_to_date() {
943            trace!(?out_dir, "enzyme build artifacts are up to date");
944            if stamp.stamp().is_empty() {
945                builder.info(
946                    "Could not determine the Enzyme submodule commit hash. \
947                     Assuming that an Enzyme rebuild is not necessary.",
948                );
949                builder.info(&format!(
950                    "To force Enzyme to rebuild, remove the file `{}`",
951                    stamp.path().display()
952                ));
953            }
954            return out_dir;
955        }
956
957        trace!(?target, "(re)building enzyme artifacts");
958        builder.info(&format!("Building Enzyme for {target}"));
959        t!(stamp.remove());
960        let _time = helpers::timeit(builder);
961        t!(fs::create_dir_all(&out_dir));
962
963        builder
964            .config
965            .update_submodule(Path::new("src").join("tools").join("enzyme").to_str().unwrap());
966        let mut cfg = cmake::Config::new(builder.src.join("src/tools/enzyme/enzyme/"));
967        configure_cmake(builder, target, &mut cfg, true, LdFlags::default(), &[]);
968
969        // Re-use the same flags as llvm to control the level of debug information
970        // generated by Enzyme.
971        // FIXME(ZuseZ4): Find a nicer way to use Enzyme Debug builds.
972        let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
973            (false, _) => "Debug",
974            (true, false) => "Release",
975            (true, true) => "RelWithDebInfo",
976        };
977        trace!(?profile);
978
979        cfg.out_dir(&out_dir)
980            .profile(profile)
981            .env("LLVM_CONFIG_REAL", &host_llvm_config)
982            .define("LLVM_ENABLE_ASSERTIONS", "ON")
983            .define("ENZYME_EXTERNAL_SHARED_LIB", "ON")
984            .define("ENZYME_BC_LOADER", "OFF")
985            .define("LLVM_DIR", builder.llvm_out(target));
986
987        cfg.build();
988
989        t!(stamp.write());
990        out_dir
991    }
992}
993
994#[derive(Debug, Clone, Hash, PartialEq, Eq)]
995pub struct Lld {
996    pub target: TargetSelection,
997}
998
999impl Step for Lld {
1000    type Output = PathBuf;
1001    const IS_HOST: bool = true;
1002
1003    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1004        run.path("src/llvm-project/lld")
1005    }
1006
1007    fn make_run(run: RunConfig<'_>) {
1008        run.builder.ensure(Lld { target: run.target });
1009    }
1010
1011    /// Compile LLD for `target`.
1012    fn run(self, builder: &Builder<'_>) -> PathBuf {
1013        if builder.config.dry_run() {
1014            return PathBuf::from("lld-out-dir-test-gen");
1015        }
1016        let target = self.target;
1017
1018        let LlvmResult { host_llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target });
1019
1020        // The `dist` step packages LLD next to LLVM's binaries for download-ci-llvm. The root path
1021        // we usually expect here is `./build/$triple/ci-llvm/`, with the binaries in its `bin`
1022        // subfolder. We check if that's the case, and if LLD's binary already exists there next to
1023        // `llvm-config`: if so, we can use it instead of building LLVM/LLD from source.
1024        let ci_llvm_bin = host_llvm_config.parent().unwrap();
1025        if ci_llvm_bin.is_dir() && ci_llvm_bin.file_name().unwrap() == "bin" {
1026            let lld_path = ci_llvm_bin.join(exe("lld", target));
1027            if lld_path.exists() {
1028                // The following steps copying `lld` as `rust-lld` to the sysroot, expect it in the
1029                // `bin` subfolder of this step's out dir.
1030                return ci_llvm_bin.parent().unwrap().to_path_buf();
1031            }
1032        }
1033
1034        let out_dir = builder.lld_out(target);
1035
1036        let lld_stamp = BuildStamp::new(&out_dir).with_prefix("lld");
1037        if lld_stamp.path().exists() {
1038            return out_dir;
1039        }
1040
1041        let _guard = builder.msg_unstaged(Kind::Build, "LLD", target);
1042        let _time = helpers::timeit(builder);
1043        t!(fs::create_dir_all(&out_dir));
1044
1045        let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/lld"));
1046        let mut ldflags = LdFlags::default();
1047
1048        // When building LLD as part of a build with instrumentation on windows, for example
1049        // when doing PGO on CI, cmake or clang-cl don't automatically link clang's
1050        // profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid
1051        // linking errors, much like LLVM's cmake setup does in that situation.
1052        if builder.config.llvm_profile_generate
1053            && target.is_msvc()
1054            && let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref()
1055        {
1056            // Find clang's runtime library directory and push that as a search path to the
1057            // cmake linker flags.
1058            let clang_rt_dir = get_clang_cl_resource_dir(builder, clang_cl_path);
1059            ldflags.push_all(format!("/libpath:{}", clang_rt_dir.display()));
1060        }
1061
1062        // LLD is built as an LLVM tool, but is distributed outside of the `llvm-tools` component,
1063        // which impacts where it expects to find LLVM's shared library. This causes #80703.
1064        //
1065        // LLD is distributed at "$root/lib/rustlib/$host/bin/rust-lld", but the `libLLVM-*.so` it
1066        // needs is distributed at "$root/lib". The default rpath of "$ORIGIN/../lib" points at the
1067        // lib path for LLVM tools, not the one for rust binaries.
1068        //
1069        // (The `llvm-tools` component copies the .so there for the other tools, and with that
1070        // component installed, one can successfully invoke `rust-lld` directly without rustup's
1071        // `LD_LIBRARY_PATH` overrides)
1072        //
1073        if builder.config.rpath_enabled(target)
1074            && helpers::use_host_linker(target)
1075            && builder.config.llvm_link_shared()
1076            && target.contains("linux")
1077        {
1078            // So we inform LLD where it can find LLVM's libraries by adding an rpath entry to the
1079            // expected parent `lib` directory.
1080            //
1081            // Be careful when changing this path, we need to ensure it's quoted or escaped:
1082            // `$ORIGIN` would otherwise be expanded when the `LdFlags` are passed verbatim to
1083            // cmake.
1084            ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'");
1085        }
1086
1087        configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
1088        configure_llvm(builder, target, &mut cfg);
1089
1090        // Re-use the same flags as llvm to control the level of debug information
1091        // generated for lld.
1092        let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
1093            (false, _) => "Debug",
1094            (true, false) => "Release",
1095            (true, true) => "RelWithDebInfo",
1096        };
1097
1098        cfg.out_dir(&out_dir)
1099            .profile(profile)
1100            .define("LLVM_CMAKE_DIR", llvm_cmake_dir)
1101            .define("LLVM_INCLUDE_TESTS", "OFF");
1102
1103        if !builder.config.is_host_target(target) {
1104            // Use the host llvm-tblgen binary.
1105            cfg.define(
1106                "LLVM_TABLEGEN_EXE",
1107                host_llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
1108            );
1109        }
1110
1111        cfg.build();
1112
1113        t!(lld_stamp.write());
1114        out_dir
1115    }
1116}
1117
1118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1119pub struct Sanitizers {
1120    pub target: TargetSelection,
1121}
1122
1123impl Step for Sanitizers {
1124    type Output = Vec<SanitizerRuntime>;
1125
1126    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1127        run.alias("sanitizers")
1128    }
1129
1130    fn make_run(run: RunConfig<'_>) {
1131        run.builder.ensure(Sanitizers { target: run.target });
1132    }
1133
1134    /// Builds sanitizer runtime libraries.
1135    fn run(self, builder: &Builder<'_>) -> Self::Output {
1136        let compiler_rt_dir = builder.src.join("src/llvm-project/compiler-rt");
1137        if !compiler_rt_dir.exists() {
1138            return Vec::new();
1139        }
1140
1141        let out_dir = builder.native_dir(self.target).join("sanitizers");
1142        let runtimes = supported_sanitizers(&out_dir, self.target, &builder.config.channel);
1143
1144        if builder.config.dry_run() || runtimes.is_empty() {
1145            return runtimes;
1146        }
1147
1148        let LlvmResult { host_llvm_config, .. } =
1149            builder.ensure(Llvm { target: builder.config.host_target });
1150
1151        static STAMP_HASH_MEMO: OnceLock<String> = OnceLock::new();
1152        let smart_stamp_hash = STAMP_HASH_MEMO.get_or_init(|| {
1153            generate_smart_stamp_hash(
1154                builder,
1155                &builder.config.src.join("src/llvm-project/compiler-rt"),
1156                builder.in_tree_llvm_info.sha().unwrap_or_default(),
1157            )
1158        });
1159
1160        let stamp = BuildStamp::new(&out_dir).with_prefix("sanitizers").add_stamp(smart_stamp_hash);
1161
1162        if stamp.is_up_to_date() {
1163            if stamp.stamp().is_empty() {
1164                builder.info(&format!(
1165                    "Rebuild sanitizers by removing the file `{}`",
1166                    stamp.path().display()
1167                ));
1168            }
1169
1170            return runtimes;
1171        }
1172
1173        let _guard = builder.msg_unstaged(Kind::Build, "sanitizers", self.target);
1174        t!(stamp.remove());
1175        let _time = helpers::timeit(builder);
1176
1177        let mut cfg = cmake::Config::new(&compiler_rt_dir);
1178        cfg.profile("Release");
1179        cfg.define("CMAKE_C_COMPILER_TARGET", self.target.triple);
1180        cfg.define("COMPILER_RT_BUILD_BUILTINS", "OFF");
1181        cfg.define("COMPILER_RT_BUILD_CRT", "OFF");
1182        cfg.define("COMPILER_RT_BUILD_LIBFUZZER", "OFF");
1183        cfg.define("COMPILER_RT_BUILD_PROFILE", "OFF");
1184        cfg.define("COMPILER_RT_BUILD_SANITIZERS", "ON");
1185        cfg.define("COMPILER_RT_BUILD_XRAY", "OFF");
1186        cfg.define("COMPILER_RT_DEFAULT_TARGET_ONLY", "ON");
1187        cfg.define("COMPILER_RT_USE_LIBCXX", "OFF");
1188        cfg.define("LLVM_CONFIG_PATH", &host_llvm_config);
1189
1190        if self.target.contains("ohos") {
1191            cfg.define("COMPILER_RT_USE_BUILTINS_LIBRARY", "ON");
1192        }
1193
1194        // On Darwin targets the sanitizer runtimes are build as universal binaries.
1195        // Unfortunately sccache currently lacks support to build them successfully.
1196        // Disable compiler launcher on Darwin targets to avoid potential issues.
1197        let use_compiler_launcher = !self.target.contains("apple-darwin");
1198        // Since v1.0.86, the cc crate adds -mmacosx-version-min to the default
1199        // flags on MacOS. A long-standing bug in the CMake rules for compiler-rt
1200        // causes architecture detection to be skipped when this flag is present,
1201        // and compilation fails. https://github.com/llvm/llvm-project/issues/88780
1202        let suppressed_compiler_flag_prefixes: &[&str] =
1203            if self.target.contains("apple-darwin") { &["-mmacosx-version-min="] } else { &[] };
1204        configure_cmake(
1205            builder,
1206            self.target,
1207            &mut cfg,
1208            use_compiler_launcher,
1209            LdFlags::default(),
1210            suppressed_compiler_flag_prefixes,
1211        );
1212
1213        t!(fs::create_dir_all(&out_dir));
1214        cfg.out_dir(out_dir);
1215
1216        for runtime in &runtimes {
1217            cfg.build_target(&runtime.cmake_target);
1218            cfg.build();
1219        }
1220        t!(stamp.write());
1221
1222        runtimes
1223    }
1224}
1225
1226#[derive(Clone, Debug)]
1227pub struct SanitizerRuntime {
1228    /// CMake target used to build the runtime.
1229    pub cmake_target: String,
1230    /// Path to the built runtime library.
1231    pub path: PathBuf,
1232    /// Library filename that will be used rustc.
1233    pub name: String,
1234}
1235
1236/// Returns sanitizers available on a given target.
1237fn supported_sanitizers(
1238    out_dir: &Path,
1239    target: TargetSelection,
1240    channel: &str,
1241) -> Vec<SanitizerRuntime> {
1242    let darwin_libs = |os: &str, components: &[&str]| -> Vec<SanitizerRuntime> {
1243        components
1244            .iter()
1245            .map(move |c| SanitizerRuntime {
1246                cmake_target: format!("clang_rt.{c}_{os}_dynamic"),
1247                path: out_dir.join(format!("build/lib/darwin/libclang_rt.{c}_{os}_dynamic.dylib")),
1248                name: format!("librustc-{channel}_rt.{c}.dylib"),
1249            })
1250            .collect()
1251    };
1252
1253    let common_libs = |os: &str, arch: &str, components: &[&str]| -> Vec<SanitizerRuntime> {
1254        components
1255            .iter()
1256            .map(move |c| SanitizerRuntime {
1257                cmake_target: format!("clang_rt.{c}-{arch}"),
1258                path: out_dir.join(format!("build/lib/{os}/libclang_rt.{c}-{arch}.a")),
1259                name: format!("librustc-{channel}_rt.{c}.a"),
1260            })
1261            .collect()
1262    };
1263
1264    match &*target.triple {
1265        "aarch64-apple-darwin" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
1266        "aarch64-apple-ios" => darwin_libs("ios", &["asan", "tsan"]),
1267        "aarch64-apple-ios-sim" => darwin_libs("iossim", &["asan", "tsan"]),
1268        "aarch64-apple-ios-macabi" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
1269        "aarch64-unknown-fuchsia" => common_libs("fuchsia", "aarch64", &["asan"]),
1270        "aarch64-unknown-linux-gnu" => {
1271            common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan", "hwasan"])
1272        }
1273        "aarch64-unknown-linux-ohos" => {
1274            common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan", "hwasan"])
1275        }
1276        "loongarch64-unknown-linux-gnu" | "loongarch64-unknown-linux-musl" => {
1277            common_libs("linux", "loongarch64", &["asan", "lsan", "msan", "tsan"])
1278        }
1279        "x86_64-apple-darwin" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
1280        "x86_64-unknown-fuchsia" => common_libs("fuchsia", "x86_64", &["asan"]),
1281        "x86_64-apple-ios" => darwin_libs("iossim", &["asan", "tsan"]),
1282        "x86_64-apple-ios-macabi" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
1283        "x86_64-unknown-freebsd" => common_libs("freebsd", "x86_64", &["asan", "msan", "tsan"]),
1284        "x86_64-unknown-netbsd" => {
1285            common_libs("netbsd", "x86_64", &["asan", "lsan", "msan", "tsan"])
1286        }
1287        "x86_64-unknown-illumos" => common_libs("illumos", "x86_64", &["asan"]),
1288        "x86_64-pc-solaris" => common_libs("solaris", "x86_64", &["asan"]),
1289        "x86_64-unknown-linux-gnu" => {
1290            common_libs("linux", "x86_64", &["asan", "dfsan", "lsan", "msan", "safestack", "tsan"])
1291        }
1292        "x86_64-unknown-linux-musl" => {
1293            common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])
1294        }
1295        "s390x-unknown-linux-gnu" => {
1296            common_libs("linux", "s390x", &["asan", "lsan", "msan", "tsan"])
1297        }
1298        "s390x-unknown-linux-musl" => {
1299            common_libs("linux", "s390x", &["asan", "lsan", "msan", "tsan"])
1300        }
1301        "x86_64-unknown-linux-ohos" => {
1302            common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])
1303        }
1304        _ => Vec::new(),
1305    }
1306}
1307
1308#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1309pub struct CrtBeginEnd {
1310    pub target: TargetSelection,
1311}
1312
1313impl Step for CrtBeginEnd {
1314    type Output = PathBuf;
1315
1316    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1317        run.path("src/llvm-project/compiler-rt/lib/crt")
1318    }
1319
1320    fn make_run(run: RunConfig<'_>) {
1321        if run.target.needs_crt_begin_end() {
1322            run.builder.ensure(CrtBeginEnd { target: run.target });
1323        }
1324    }
1325
1326    /// Build crtbegin.o/crtend.o for musl target.
1327    fn run(self, builder: &Builder<'_>) -> Self::Output {
1328        builder.require_submodule(
1329            "src/llvm-project",
1330            Some("The LLVM sources are required for the CRT from `compiler-rt`."),
1331        );
1332
1333        let out_dir = builder.native_dir(self.target).join("crt");
1334
1335        if builder.config.dry_run() {
1336            return out_dir;
1337        }
1338
1339        let crtbegin_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtbegin.c");
1340        let crtend_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtend.c");
1341        if up_to_date(&crtbegin_src, &out_dir.join("crtbeginS.o"))
1342            && up_to_date(&crtend_src, &out_dir.join("crtendS.o"))
1343        {
1344            return out_dir;
1345        }
1346
1347        let _guard = builder.msg_unstaged(Kind::Build, "crtbegin.o and crtend.o", self.target);
1348        t!(fs::create_dir_all(&out_dir));
1349
1350        let mut cfg = cc::Build::new();
1351
1352        if let Some(ar) = builder.ar(self.target) {
1353            cfg.archiver(ar);
1354        }
1355        cfg.compiler(builder.cc(self.target));
1356        cfg.cargo_metadata(false)
1357            .out_dir(&out_dir)
1358            .target(&self.target.triple)
1359            .host(&builder.config.host_target.triple)
1360            .warnings(false)
1361            .debug(false)
1362            .opt_level(3)
1363            .file(crtbegin_src)
1364            .file(crtend_src);
1365
1366        // Those flags are defined in src/llvm-project/compiler-rt/lib/builtins/CMakeLists.txt
1367        // Currently only consumer of those objects is musl, which use .init_array/.fini_array
1368        // instead of .ctors/.dtors
1369        cfg.flag("-std=c11")
1370            .define("CRT_HAS_INITFINI_ARRAY", None)
1371            .define("EH_USE_FRAME_REGISTRY", None);
1372
1373        let objs = cfg.compile_intermediates();
1374        assert_eq!(objs.len(), 2);
1375        for obj in objs {
1376            let base_name = unhashed_basename(&obj);
1377            assert!(base_name == "crtbegin" || base_name == "crtend");
1378            t!(fs::copy(&obj, out_dir.join(format!("{base_name}S.o"))));
1379            t!(fs::rename(&obj, out_dir.join(format!("{base_name}.o"))));
1380        }
1381
1382        out_dir
1383    }
1384}
1385
1386#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1387pub struct Libunwind {
1388    pub target: TargetSelection,
1389}
1390
1391impl Step for Libunwind {
1392    type Output = PathBuf;
1393
1394    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1395        run.path("src/llvm-project/libunwind")
1396    }
1397
1398    fn make_run(run: RunConfig<'_>) {
1399        run.builder.ensure(Libunwind { target: run.target });
1400    }
1401
1402    /// Build libunwind.a
1403    fn run(self, builder: &Builder<'_>) -> Self::Output {
1404        builder.require_submodule(
1405            "src/llvm-project",
1406            Some("The LLVM sources are required for libunwind."),
1407        );
1408
1409        if builder.config.dry_run() {
1410            return PathBuf::new();
1411        }
1412
1413        let out_dir = builder.native_dir(self.target).join("libunwind");
1414        let root = builder.src.join("src/llvm-project/libunwind");
1415
1416        if up_to_date(&root, &out_dir.join("libunwind.a")) {
1417            return out_dir;
1418        }
1419
1420        let _guard = builder.msg_unstaged(Kind::Build, "libunwind.a", self.target);
1421        t!(fs::create_dir_all(&out_dir));
1422
1423        let mut cc_cfg = cc::Build::new();
1424        let mut cpp_cfg = cc::Build::new();
1425
1426        cpp_cfg.cpp(true);
1427        cpp_cfg.cpp_set_stdlib(None);
1428        cpp_cfg.flag("-nostdinc++");
1429        cpp_cfg.flag("-fno-exceptions");
1430        cpp_cfg.flag("-fno-rtti");
1431        cpp_cfg.flag_if_supported("-fvisibility-global-new-delete-hidden");
1432
1433        for cfg in [&mut cc_cfg, &mut cpp_cfg].iter_mut() {
1434            if let Some(ar) = builder.ar(self.target) {
1435                cfg.archiver(ar);
1436            }
1437            cfg.target(&self.target.triple);
1438            cfg.host(&builder.config.host_target.triple);
1439            cfg.warnings(false);
1440            cfg.debug(false);
1441            // get_compiler() need set opt_level first.
1442            cfg.opt_level(3);
1443            cfg.flag("-fstrict-aliasing");
1444            cfg.flag("-funwind-tables");
1445            cfg.flag("-fvisibility=hidden");
1446            cfg.define("_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS", None);
1447            cfg.define("_LIBUNWIND_IS_NATIVE_ONLY", "1");
1448            cfg.include(root.join("include"));
1449            cfg.cargo_metadata(false);
1450            cfg.out_dir(&out_dir);
1451
1452            if self.target.contains("x86_64-fortanix-unknown-sgx") {
1453                cfg.static_flag(true);
1454                cfg.flag("-fno-stack-protector");
1455                cfg.flag("-ffreestanding");
1456                cfg.flag("-fexceptions");
1457
1458                // easiest way to undefine since no API available in cc::Build to undefine
1459                cfg.flag("-U_FORTIFY_SOURCE");
1460                cfg.define("_FORTIFY_SOURCE", "0");
1461                cfg.define("RUST_SGX", "1");
1462                cfg.define("__NO_STRING_INLINES", None);
1463                cfg.define("__NO_MATH_INLINES", None);
1464                cfg.define("_LIBUNWIND_IS_BAREMETAL", None);
1465                cfg.define("NDEBUG", None);
1466            }
1467            if self.target.is_windows() {
1468                cfg.define("_LIBUNWIND_HIDE_SYMBOLS", "1");
1469            }
1470        }
1471
1472        cc_cfg.compiler(builder.cc(self.target));
1473        if let Ok(cxx) = builder.cxx(self.target) {
1474            cpp_cfg.compiler(cxx);
1475        } else {
1476            cc_cfg.compiler(builder.cc(self.target));
1477        }
1478
1479        // Don't set this for clang
1480        // By default, Clang builds C code in GNU C17 mode.
1481        // By default, Clang builds C++ code according to the C++98 standard,
1482        // with many C++11 features accepted as extensions.
1483        if cc_cfg.get_compiler().is_like_gnu() {
1484            cc_cfg.flag("-std=c99");
1485        }
1486        if cpp_cfg.get_compiler().is_like_gnu() {
1487            cpp_cfg.flag("-std=c++11");
1488        }
1489
1490        if self.target.contains("x86_64-fortanix-unknown-sgx") || self.target.contains("musl") {
1491            // use the same GCC C compiler command to compile C++ code so we do not need to setup the
1492            // C++ compiler env variables on the builders.
1493            // Don't set this for clang++, as clang++ is able to compile this without libc++.
1494            if cpp_cfg.get_compiler().is_like_gnu() {
1495                cpp_cfg.cpp(false);
1496                cpp_cfg.compiler(builder.cc(self.target));
1497            }
1498        }
1499
1500        let mut c_sources = vec![
1501            "Unwind-sjlj.c",
1502            "UnwindLevel1-gcc-ext.c",
1503            "UnwindLevel1.c",
1504            "UnwindRegistersRestore.S",
1505            "UnwindRegistersSave.S",
1506        ];
1507
1508        let cpp_sources = vec!["Unwind-EHABI.cpp", "Unwind-seh.cpp", "libunwind.cpp"];
1509        let cpp_len = cpp_sources.len();
1510
1511        if self.target.contains("x86_64-fortanix-unknown-sgx") {
1512            c_sources.push("UnwindRustSgx.c");
1513        }
1514
1515        for src in c_sources {
1516            cc_cfg.file(root.join("src").join(src).canonicalize().unwrap());
1517        }
1518
1519        for src in &cpp_sources {
1520            cpp_cfg.file(root.join("src").join(src).canonicalize().unwrap());
1521        }
1522
1523        cpp_cfg.compile("unwind-cpp");
1524
1525        // FIXME: https://github.com/alexcrichton/cc-rs/issues/545#issuecomment-679242845
1526        let mut count = 0;
1527        let mut files = fs::read_dir(&out_dir)
1528            .unwrap()
1529            .map(|entry| entry.unwrap().path().canonicalize().unwrap())
1530            .collect::<Vec<_>>();
1531        files.sort();
1532        for file in files {
1533            if file.is_file() && file.extension() == Some(OsStr::new("o")) {
1534                // Object file name without the hash prefix is "Unwind-EHABI", "Unwind-seh" or "libunwind".
1535                let base_name = unhashed_basename(&file);
1536                if cpp_sources.iter().any(|f| *base_name == f[..f.len() - 4]) {
1537                    cc_cfg.object(&file);
1538                    count += 1;
1539                }
1540            }
1541        }
1542        assert_eq!(cpp_len, count, "Can't get object files from {out_dir:?}");
1543
1544        cc_cfg.compile("unwind");
1545        out_dir
1546    }
1547}