Skip to main content

bootstrap/core/config/toml/
rust.rs

1//! This module defines the `Rust` struct, which represents the `[rust]` table
2//! in the `bootstrap.toml` configuration file.
3
4use build_helper::ci::CiEnv;
5use serde::{Deserialize, Deserializer};
6
7use crate::core::config::toml::TomlConfig;
8use crate::core::config::{
9    CompressDebuginfo, DebuginfoLevel, Merge, OverrideAllocator, ReplaceOpt, StringOrBool,
10};
11use crate::{BTreeSet, CodegenBackendKind, HashSet, PathBuf, TargetSelection, define_config, exit};
12
13define_config! {
14    /// TOML representation of how the Rust build is configured.
15    #[derive(Default)]
16    struct Rust {
17        optimize: Option<RustOptimize> = "optimize",
18        debug: Option<bool> = "debug",
19        codegen_units: Option<u32> = "codegen-units",
20        codegen_units_std: Option<u32> = "codegen-units-std",
21        rustc_debug_assertions: Option<bool> = "debug-assertions",
22        randomize_layout: Option<bool> = "randomize-layout",
23        std_debug_assertions: Option<bool> = "debug-assertions-std",
24        tools_debug_assertions: Option<bool> = "debug-assertions-tools",
25        overflow_checks: Option<bool> = "overflow-checks",
26        overflow_checks_std: Option<bool> = "overflow-checks-std",
27        debug_logging: Option<bool> = "debug-logging",
28        debuginfo_level: Option<DebuginfoLevel> = "debuginfo-level",
29        debuginfo_level_rustc: Option<DebuginfoLevel> = "debuginfo-level-rustc",
30        debuginfo_level_std: Option<DebuginfoLevel> = "debuginfo-level-std",
31        debuginfo_level_tools: Option<DebuginfoLevel> = "debuginfo-level-tools",
32        debuginfo_level_tests: Option<DebuginfoLevel> = "debuginfo-level-tests",
33        compress_debuginfo: Option<CompressDebuginfo> = "compress-debuginfo",
34        backtrace: Option<bool> = "backtrace",
35        incremental: Option<bool> = "incremental",
36        default_linker: Option<String> = "default-linker",
37        channel: Option<String> = "channel",
38        musl_root: Option<String> = "musl-root",
39        rpath: Option<bool> = "rpath",
40        rustflags: Option<Vec<String>> = "rustflags",
41        strip: Option<bool> = "strip",
42        frame_pointers: Option<bool> = "frame-pointers",
43        stack_protector: Option<String> = "stack-protector",
44        verbose_tests: Option<bool> = "verbose-tests",
45        optimize_tests: Option<bool> = "optimize-tests",
46        codegen_tests: Option<bool> = "codegen-tests",
47        omit_git_hash: Option<bool> = "omit-git-hash",
48        dist_src: Option<bool> = "dist-src",
49        save_toolstates: Option<String> = "save-toolstates",
50        codegen_backends: Option<Vec<String>> = "codegen-backends",
51        llvm_bitcode_linker: Option<bool> = "llvm-bitcode-linker",
52        lld: Option<bool> = "lld",
53        bootstrap_override_lld: Option<BootstrapOverrideLld> = "bootstrap-override-lld",
54        // FIXME: Remove this option in Spring 2026
55        bootstrap_override_lld_legacy: Option<BootstrapOverrideLld> = "use-lld",
56        llvm_tools: Option<bool> = "llvm-tools",
57        deny_warnings: Option<bool> = "deny-warnings",
58        backtrace_on_ice: Option<bool> = "backtrace-on-ice",
59        verify_llvm_ir: Option<bool> = "verify-llvm-ir",
60        thin_lto_import_instr_limit: Option<u32> = "thin-lto-import-instr-limit",
61        remap_debuginfo: Option<bool> = "remap-debuginfo",
62        override_allocator: Option<OverrideAllocator> = "override-allocator",
63        // FIXME: Remove this option in Q1 2027
64        jemalloc: Option<bool> = "jemalloc",
65        test_compare_mode: Option<bool> = "test-compare-mode",
66        llvm_libunwind: Option<String> = "llvm-libunwind",
67        control_flow_guard: Option<bool> = "control-flow-guard",
68        ehcont_guard: Option<bool> = "ehcont-guard",
69        new_symbol_mangling: Option<bool> = "new-symbol-mangling",
70        annotate_moves_size_limit: Option<u64> = "annotate-moves-size-limit",
71        // FIXME: Remove this option at the end of 2026
72        profile_generate: Option<PathBuf> = "profile-generate",
73        // FIXME: Remove this option at the end of 2026
74        profile_use: Option<PathBuf> = "profile-use",
75        // ignored; this is set from an env var set by bootstrap.py
76        download_rustc: Option<StringOrBool> = "download-rustc",
77        lto: Option<String> = "lto",
78        validate_mir_opts: Option<u32> = "validate-mir-opts",
79        std_features: Option<BTreeSet<String>> = "std-features",
80        break_on_ice: Option<bool> = "break-on-ice",
81        parallel_frontend_threads: Option<u32> = "parallel-frontend-threads",
82    }
83}
84
85/// Determines if we should override the linker used for linking Rust code built
86/// during the bootstrapping process to be LLD.
87///
88/// The primary use-case for this is to make local (re)builds of Rust code faster
89/// when using bootstrap.
90///
91/// This does not affect the *behavior* of the built/distributed compiler when invoked
92/// outside of bootstrap.
93/// It might affect its performance/binary size though, as that can depend on the
94/// linker that links rustc.
95///
96/// There are two ways of overriding the linker to be LLD:
97/// - Self-contained LLD: use `rust-lld` from the compiler's sysroot
98/// - External: use an external `lld` binary
99///
100/// It is configured depending on the target:
101/// 1) Everything except MSVC
102/// - Self-contained: `-Clinker-features=+lld -Clink-self-contained=+linker`
103/// - External: `-Clinker-features=+lld`
104/// 2) MSVC
105/// - Self-contained: `-Clinker=<path to rust-lld>`
106/// - External: `-Clinker=lld`
107#[derive(Copy, Clone, Default, Debug, PartialEq)]
108pub enum BootstrapOverrideLld {
109    /// Do not override the linker LLD
110    #[default]
111    None,
112    /// Use `rust-lld` from the compiler's sysroot
113    SelfContained,
114    /// Use an externally provided `lld` binary.
115    /// Note that the linker name cannot be overridden, the binary has to be named `lld` and it has
116    /// to be in $PATH.
117    External,
118}
119
120impl BootstrapOverrideLld {
121    pub fn is_used(&self) -> bool {
122        match self {
123            BootstrapOverrideLld::SelfContained | BootstrapOverrideLld::External => true,
124            BootstrapOverrideLld::None => false,
125        }
126    }
127}
128
129impl<'de> Deserialize<'de> for BootstrapOverrideLld {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: Deserializer<'de>,
133    {
134        struct LldModeVisitor;
135
136        impl serde::de::Visitor<'_> for LldModeVisitor {
137            type Value = BootstrapOverrideLld;
138
139            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140                formatter.write_str("one of true, 'self-contained' or 'external'")
141            }
142
143            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
144            where
145                E: serde::de::Error,
146            {
147                Ok(if v { BootstrapOverrideLld::External } else { BootstrapOverrideLld::None })
148            }
149
150            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
151            where
152                E: serde::de::Error,
153            {
154                match v {
155                    "external" => Ok(BootstrapOverrideLld::External),
156                    "self-contained" => Ok(BootstrapOverrideLld::SelfContained),
157                    _ => Err(E::custom(format!("unknown mode {v}"))),
158                }
159            }
160        }
161
162        deserializer.deserialize_any(LldModeVisitor)
163    }
164}
165
166#[derive(Clone, Debug, PartialEq, Eq)]
167pub enum RustOptimize {
168    String(String),
169    Int(u8),
170    Bool(bool),
171}
172
173impl Default for RustOptimize {
174    fn default() -> RustOptimize {
175        RustOptimize::Bool(false)
176    }
177}
178
179impl<'de> Deserialize<'de> for RustOptimize {
180    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181    where
182        D: Deserializer<'de>,
183    {
184        deserializer.deserialize_any(OptimizeVisitor)
185    }
186}
187
188struct OptimizeVisitor;
189
190impl serde::de::Visitor<'_> for OptimizeVisitor {
191    type Value = RustOptimize;
192
193    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        formatter.write_str(r#"one of: 0, 1, 2, 3, "s", "z", true, false"#)
195    }
196
197    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
198    where
199        E: serde::de::Error,
200    {
201        if matches!(value, "s" | "z") {
202            Ok(RustOptimize::String(value.to_string()))
203        } else {
204            Err(serde::de::Error::custom(format_optimize_error_msg(value)))
205        }
206    }
207
208    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
209    where
210        E: serde::de::Error,
211    {
212        if matches!(value, 0..=3) {
213            Ok(RustOptimize::Int(value as u8))
214        } else {
215            Err(serde::de::Error::custom(format_optimize_error_msg(value)))
216        }
217    }
218
219    fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
220    where
221        E: serde::de::Error,
222    {
223        Ok(RustOptimize::Bool(value))
224    }
225}
226
227fn format_optimize_error_msg(v: impl std::fmt::Display) -> String {
228    format!(
229        r#"unrecognized option for rust optimize: "{v}", expected one of 0, 1, 2, 3, "s", "z", true, false"#
230    )
231}
232
233impl RustOptimize {
234    pub(crate) fn is_release(&self) -> bool {
235        match &self {
236            RustOptimize::Bool(true) | RustOptimize::String(_) => true,
237            RustOptimize::Int(i) => *i > 0,
238            RustOptimize::Bool(false) => false,
239        }
240    }
241
242    pub(crate) fn get_opt_level(&self) -> Option<String> {
243        match &self {
244            RustOptimize::String(s) => Some(s.clone()),
245            RustOptimize::Int(i) => Some(i.to_string()),
246            RustOptimize::Bool(_) => None,
247        }
248    }
249}
250
251/// Compares the current Rust options against those in the CI rustc builder and detects any incompatible options.
252/// It does this by destructuring the `Rust` instance to make sure every `Rust` field is covered and not missing.
253pub fn check_incompatible_options_for_ci_rustc(
254    host: TargetSelection,
255    current_config_toml: TomlConfig,
256    ci_config_toml: TomlConfig,
257) -> Result<(), String> {
258    macro_rules! err {
259        ($current:expr, $expected:expr, $config_section:expr) => {
260            if let Some(current) = &$current {
261                if Some(current) != $expected.as_ref() {
262                    return Err(format!(
263                        "ERROR: Setting `{}` is incompatible with `rust.download-rustc`. \
264                        Current value: {:?}, Expected value(s): {}{:?}",
265                        format!("{}.{}", $config_section, stringify!($expected).replace("_", "-")),
266                        $current,
267                        if $expected.is_some() { "None/" } else { "" },
268                        $expected,
269                    ));
270                };
271            };
272        };
273    }
274
275    macro_rules! warn {
276        ($current:expr, $expected:expr, $config_section:expr) => {
277            if let Some(current) = &$current {
278                if Some(current) != $expected.as_ref() {
279                    println!(
280                        "WARNING: `{}` has no effect with `rust.download-rustc`. \
281                        Current value: {:?}, Expected value(s): {}{:?}",
282                        format!("{}.{}", $config_section, stringify!($expected).replace("_", "-")),
283                        $current,
284                        if $expected.is_some() { "None/" } else { "" },
285                        $expected,
286                    );
287                };
288            };
289        };
290    }
291
292    let current_profiler = current_config_toml.build.as_ref().and_then(|b| b.profiler);
293    let profiler = ci_config_toml.build.as_ref().and_then(|b| b.profiler);
294    err!(current_profiler, profiler, "build");
295
296    let current_optimized_compiler_builtins =
297        current_config_toml.build.as_ref().and_then(|b| b.optimized_compiler_builtins.clone());
298    let optimized_compiler_builtins =
299        ci_config_toml.build.as_ref().and_then(|b| b.optimized_compiler_builtins.clone());
300    err!(current_optimized_compiler_builtins, optimized_compiler_builtins, "build");
301
302    // We always build the in-tree compiler on cross targets, so we only care
303    // about the host target here.
304    let host_str = host.to_string();
305    if let Some(current_cfg) = current_config_toml.target.as_ref().and_then(|c| c.get(&host_str))
306        && current_cfg.profiler.is_some()
307    {
308        let ci_target_toml = ci_config_toml.target.as_ref().and_then(|c| c.get(&host_str));
309        let ci_cfg = ci_target_toml.ok_or(format!(
310            "Target specific config for '{host_str}' is not present for CI-rustc"
311        ))?;
312
313        let profiler = &ci_cfg.profiler;
314        err!(current_cfg.profiler, profiler, "build");
315
316        let optimized_compiler_builtins = &ci_cfg.optimized_compiler_builtins;
317        err!(current_cfg.optimized_compiler_builtins, optimized_compiler_builtins, "build");
318    }
319
320    let (Some(current_rust_config), Some(ci_rust_config)) =
321        (current_config_toml.rust, ci_config_toml.rust)
322    else {
323        return Ok(());
324    };
325
326    let Rust {
327        // Following options are the CI rustc incompatible ones.
328        optimize,
329        randomize_layout,
330        debug_logging,
331        debuginfo_level_rustc,
332        compress_debuginfo,
333        llvm_tools,
334        llvm_bitcode_linker,
335        stack_protector,
336        strip,
337        jemalloc,
338        override_allocator,
339        rpath,
340        channel,
341        default_linker,
342        std_features,
343
344        // Rest of the options can simply be ignored.
345        incremental: _,
346        debug: _,
347        codegen_units: _,
348        codegen_units_std: _,
349        rustc_debug_assertions: _,
350        std_debug_assertions: _,
351        tools_debug_assertions: _,
352        overflow_checks: _,
353        overflow_checks_std: _,
354        debuginfo_level: _,
355        debuginfo_level_std: _,
356        debuginfo_level_tools: _,
357        debuginfo_level_tests: _,
358        backtrace: _,
359        musl_root: _,
360        verbose_tests: _,
361        optimize_tests: _,
362        codegen_tests: _,
363        omit_git_hash: _,
364        dist_src: _,
365        save_toolstates: _,
366        codegen_backends: _,
367        lld: _,
368        lto: _,
369        deny_warnings: _,
370        backtrace_on_ice: _,
371        verify_llvm_ir: _,
372        thin_lto_import_instr_limit: _,
373        remap_debuginfo: _,
374        test_compare_mode: _,
375        llvm_libunwind: _,
376        control_flow_guard: _,
377        ehcont_guard: _,
378        new_symbol_mangling: _,
379        annotate_moves_size_limit: _,
380        profile_generate: _,
381        profile_use: _,
382        download_rustc: _,
383        validate_mir_opts: _,
384        frame_pointers: _,
385        break_on_ice: _,
386        parallel_frontend_threads: _,
387        bootstrap_override_lld: _,
388        bootstrap_override_lld_legacy: _,
389        rustflags: _,
390    } = ci_rust_config;
391
392    // There are two kinds of checks for CI rustc incompatible options:
393    //    1. Checking an option that may change the compiler behaviour/output.
394    //    2. Checking an option that have no effect on the compiler behaviour/output.
395    //
396    // If the option belongs to the first category, we call `err` macro for a hard error;
397    // otherwise, we just print a warning with `warn` macro.
398
399    err!(current_rust_config.optimize, optimize, "rust");
400    err!(current_rust_config.randomize_layout, randomize_layout, "rust");
401    err!(current_rust_config.compress_debuginfo, compress_debuginfo, "rust");
402    err!(current_rust_config.debug_logging, debug_logging, "rust");
403    err!(current_rust_config.debuginfo_level_rustc, debuginfo_level_rustc, "rust");
404    err!(current_rust_config.rpath, rpath, "rust");
405    err!(current_rust_config.strip, strip, "rust");
406    err!(current_rust_config.llvm_tools, llvm_tools, "rust");
407    err!(current_rust_config.llvm_bitcode_linker, llvm_bitcode_linker, "rust");
408    err!(current_rust_config.jemalloc, jemalloc, "rust");
409    err!(current_rust_config.override_allocator, override_allocator, "rust");
410    err!(current_rust_config.default_linker, default_linker, "rust");
411    err!(current_rust_config.stack_protector, stack_protector, "rust");
412    err!(current_rust_config.std_features, std_features, "rust");
413
414    warn!(current_rust_config.channel, channel, "rust");
415
416    Ok(())
417}
418
419pub(crate) const BUILTIN_CODEGEN_BACKENDS: &[&str] = &["llvm", "cranelift", "gcc"];
420
421pub(crate) fn parse_codegen_backends(
422    backends: Vec<String>,
423    section: &str,
424) -> Vec<CodegenBackendKind> {
425    const CODEGEN_BACKEND_PREFIX: &str = "rustc_codegen_";
426
427    let mut found_backends = vec![];
428    for backend in &backends {
429        if let Some(stripped) = backend.strip_prefix(CODEGEN_BACKEND_PREFIX) {
430            panic!(
431                "Invalid value '{backend}' for '{section}.codegen-backends'. \
432                Codegen backends are defined without the '{CODEGEN_BACKEND_PREFIX}' prefix. \
433                Please, use '{stripped}' instead."
434            )
435        }
436        let backend = match backend.as_str() {
437            "llvm" => CodegenBackendKind::Llvm,
438            "cranelift" => CodegenBackendKind::Cranelift,
439            "gcc" => CodegenBackendKind::Gcc,
440            backend => CodegenBackendKind::Custom(backend.to_string()),
441        };
442
443        if found_backends.contains(&backend) {
444            panic!(
445                "Duplicate value '{}' for '{section}.codegen-backends'. \
446                Each codegen backend should only be specified once.",
447                backend.name()
448            );
449        }
450
451        if !BUILTIN_CODEGEN_BACKENDS.contains(&backend.name()) {
452            if CiEnv::is_rust_lang_managed_ci_job() {
453                eprintln!("Unknown codegen backend {}", backend.name());
454                exit!(1);
455            }
456
457            println!(
458                "HELP: '{}' for '{section}.codegen-backends' might fail. \
459                List of known codegen backends: {BUILTIN_CODEGEN_BACKENDS:?}",
460                backend.name()
461            );
462        }
463        found_backends.push(backend);
464    }
465    if found_backends.is_empty() {
466        eprintln!("ERROR: `{section}.codegen-backends` should not be set to `[]`");
467        exit!(1);
468    }
469    found_backends
470}