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