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