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