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