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