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