cc/
flags.rs

1use crate::target::TargetInfo;
2use crate::{Build, Error, ErrorKind, Tool, ToolFamily};
3use std::borrow::Cow;
4use std::ffi::OsString;
5
6#[derive(Debug, PartialEq, Default)]
7pub(crate) struct RustcCodegenFlags<'a> {
8    branch_protection: Option<&'a str>,
9    code_model: Option<&'a str>,
10    no_vectorize_loops: bool,
11    no_vectorize_slp: bool,
12    profile_generate: Option<&'a str>,
13    profile_use: Option<&'a str>,
14    control_flow_guard: Option<&'a str>,
15    lto: Option<&'a str>,
16    relocation_model: Option<&'a str>,
17    embed_bitcode: Option<bool>,
18    force_frame_pointers: Option<bool>,
19    no_redzone: Option<bool>,
20    soft_float: Option<bool>,
21    dwarf_version: Option<u32>,
22}
23
24impl<'this> RustcCodegenFlags<'this> {
25    // Parse flags obtained from CARGO_ENCODED_RUSTFLAGS
26    pub(crate) fn parse(rustflags_env: &'this str) -> Result<Self, Error> {
27        fn is_flag_prefix(flag: &str) -> bool {
28            [
29                "-Z",
30                "-C",
31                "--codegen",
32                "-L",
33                "-l",
34                "-o",
35                "-W",
36                "--warn",
37                "-A",
38                "--allow",
39                "-D",
40                "--deny",
41                "-F",
42                "--forbid",
43            ]
44            .contains(&flag)
45        }
46
47        fn handle_flag_prefix<'a>(prev: &'a str, curr: &'a str) -> (&'a str, &'a str) {
48            match prev {
49                "--codegen" | "-C" => ("-C", curr),
50                // Handle flags passed like --codegen=code-model=small
51                _ if curr.starts_with("--codegen=") => ("-C", &curr[10..]),
52                "-Z" => ("-Z", curr),
53                "-L" | "-l" | "-o" => (prev, curr),
54                // Handle lint flags
55                "-W" | "--warn" => ("-W", curr),
56                "-A" | "--allow" => ("-A", curr),
57                "-D" | "--deny" => ("-D", curr),
58                "-F" | "--forbid" => ("-F", curr),
59                _ => ("", curr),
60            }
61        }
62
63        let mut codegen_flags = Self::default();
64
65        let mut prev_prefix = None;
66        for curr in rustflags_env.split("\u{1f}") {
67            let prev = prev_prefix.take().unwrap_or("");
68            if prev.is_empty() && is_flag_prefix(curr) {
69                prev_prefix = Some(curr);
70                continue;
71            }
72
73            let (prefix, rustc_flag) = handle_flag_prefix(prev, curr);
74            codegen_flags.set_rustc_flag(prefix, rustc_flag)?;
75        }
76
77        Ok(codegen_flags)
78    }
79
80    fn set_rustc_flag(&mut self, prefix: &str, flag: &'this str) -> Result<(), Error> {
81        // Convert a textual representation of a bool-like rustc flag argument into an actual bool
82        fn arg_to_bool(arg: impl AsRef<str>) -> Option<bool> {
83            match arg.as_ref() {
84                "y" | "yes" | "on" | "true" => Some(true),
85                "n" | "no" | "off" | "false" => Some(false),
86                _ => None,
87            }
88        }
89
90        fn arg_to_u32(arg: impl AsRef<str>) -> Option<u32> {
91            arg.as_ref().parse().ok()
92        }
93
94        let (flag, value) = if let Some((flag, value)) = flag.split_once('=') {
95            (flag, Some(value))
96        } else {
97            (flag, None)
98        };
99        let flag = if prefix.is_empty() {
100            Cow::Borrowed(flag)
101        } else {
102            Cow::Owned(format!("{prefix}{flag}"))
103        };
104
105        fn flag_ok_or<'flag>(
106            flag: Option<&'flag str>,
107            msg: &'static str,
108        ) -> Result<&'flag str, Error> {
109            flag.ok_or(Error::new(ErrorKind::InvalidFlag, msg))
110        }
111
112        match flag.as_ref() {
113            // https://doc.rust-lang.org/rustc/codegen-options/index.html#code-model
114            "-Ccode-model" => {
115                self.code_model = Some(flag_ok_or(value, "-Ccode-model must have a value")?);
116            }
117            // https://doc.rust-lang.org/rustc/codegen-options/index.html#no-vectorize-loops
118            "-Cno-vectorize-loops" => self.no_vectorize_loops = true,
119            // https://doc.rust-lang.org/rustc/codegen-options/index.html#no-vectorize-slp
120            "-Cno-vectorize-slp" => self.no_vectorize_slp = true,
121            // https://doc.rust-lang.org/rustc/codegen-options/index.html#profile-generate
122            "-Cprofile-generate" => {
123                self.profile_generate =
124                    Some(flag_ok_or(value, "-Cprofile-generate must have a value")?);
125            }
126            // https://doc.rust-lang.org/rustc/codegen-options/index.html#profile-use
127            "-Cprofile-use" => {
128                self.profile_use = Some(flag_ok_or(value, "-Cprofile-use must have a value")?);
129            }
130            // https://doc.rust-lang.org/rustc/codegen-options/index.html#control-flow-guard
131            "-Ccontrol-flow-guard" => self.control_flow_guard = value.or(Some("true")),
132            // https://doc.rust-lang.org/rustc/codegen-options/index.html#lto
133            "-Clto" => self.lto = value.or(Some("true")),
134            // https://doc.rust-lang.org/rustc/codegen-options/index.html#relocation-model
135            "-Crelocation-model" => {
136                self.relocation_model =
137                    Some(flag_ok_or(value, "-Crelocation-model must have a value")?);
138            }
139            // https://doc.rust-lang.org/rustc/codegen-options/index.html#embed-bitcode
140            "-Cembed-bitcode" => self.embed_bitcode = value.map_or(Some(true), arg_to_bool),
141            // https://doc.rust-lang.org/rustc/codegen-options/index.html#force-frame-pointers
142            "-Cforce-frame-pointers" => {
143                self.force_frame_pointers = value.map_or(Some(true), arg_to_bool)
144            }
145            // https://doc.rust-lang.org/rustc/codegen-options/index.html#no-redzone
146            "-Cno-redzone" => self.no_redzone = value.map_or(Some(true), arg_to_bool),
147            // https://doc.rust-lang.org/rustc/codegen-options/index.html#soft-float
148            // Note: This flag is now deprecated in rustc.
149            "-Csoft-float" => self.soft_float = value.map_or(Some(true), arg_to_bool),
150            // https://doc.rust-lang.org/beta/unstable-book/compiler-flags/branch-protection.html
151            // FIXME: Drop the -Z variant and update the doc link once the option is stabilised
152            "-Zbranch-protection" | "-Cbranch-protection" => {
153                self.branch_protection =
154                    Some(flag_ok_or(value, "-Zbranch-protection must have a value")?);
155            }
156            // https://doc.rust-lang.org/beta/unstable-book/compiler-flags/dwarf-version.html
157            // FIXME: Drop the -Z variant and update the doc link once the option is stablized
158            "-Zdwarf-version" | "-Cdwarf-version" => {
159                self.dwarf_version = Some(value.and_then(arg_to_u32).ok_or(Error::new(
160                    ErrorKind::InvalidFlag,
161                    "-Zdwarf-version must have a value",
162                ))?);
163            }
164            _ => {}
165        }
166        Ok(())
167    }
168
169    // Rust and clang/cc don't agree on what equivalent flags should look like.
170    pub(crate) fn cc_flags(&self, build: &Build, tool: &mut Tool, target: &TargetInfo<'_>) {
171        let family = tool.family;
172        // Push `flag` to `flags` if it is supported by the currently used CC
173        let mut push_if_supported = |flag: OsString| {
174            if build
175                .is_flag_supported_inner(&flag, tool, target)
176                .unwrap_or(false)
177            {
178                tool.args.push(flag);
179            } else {
180                build.cargo_output.print_warning(&format!(
181                    "Inherited flag {:?} is not supported by the currently used CC",
182                    flag
183                ));
184            }
185        };
186
187        let clang_or_gnu =
188            matches!(family, ToolFamily::Clang { .. }) || matches!(family, ToolFamily::Gnu);
189
190        // Flags shared between clang and gnu
191        if clang_or_gnu {
192            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mbranch-protection
193            // https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html#index-mbranch-protection (Aarch64)
194            // https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html#index-mbranch-protection-1 (ARM)
195            // https://developer.arm.com/documentation/101754/0619/armclang-Reference/armclang-Command-line-Options/-mbranch-protection
196            if let Some(value) = self.branch_protection {
197                push_if_supported(
198                    format!("-mbranch-protection={}", value.replace(",", "+")).into(),
199                );
200            }
201            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mcmodel
202            // https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html (several archs, search for `-mcmodel=`).
203            // FIXME(madsmtm): Parse the model, to make sure we pass the correct value (depending on arch).
204            if let Some(value) = self.code_model {
205                push_if_supported(format!("-mcmodel={value}").into());
206            }
207            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fno-vectorize
208            // https://gcc.gnu.org/onlinedocs/gnat_ugn/Vectorization-of-loops.html
209            if self.no_vectorize_loops {
210                push_if_supported("-fno-vectorize".into());
211            }
212            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fno-slp-vectorize
213            // https://gcc.gnu.org/onlinedocs/gnat_ugn/Vectorization-of-loops.html
214            if self.no_vectorize_slp {
215                push_if_supported("-fno-slp-vectorize".into());
216            }
217            if let Some(value) = self.relocation_model {
218                let cc_flag = match value {
219                    // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fPIC
220                    // https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fPIC
221                    "pic" => Some("-fPIC"),
222                    // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fPIE
223                    // https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fPIE
224                    "pie" => Some("-fPIE"),
225                    // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mdynamic-no-pic
226                    // https://gcc.gnu.org/onlinedocs/gcc/RS_002f6000-and-PowerPC-Options.html#index-mdynamic-no-pic
227                    "dynamic-no-pic" => Some("-mdynamic-no-pic"),
228                    _ => None,
229                };
230                if let Some(cc_flag) = cc_flag {
231                    push_if_supported(cc_flag.into());
232                }
233            }
234            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fno-omit-frame-pointer
235            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fomit-frame-pointer
236            // https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fomit-frame-pointer
237            if let Some(value) = self.force_frame_pointers {
238                let cc_flag = if value {
239                    "-fno-omit-frame-pointer"
240                } else {
241                    "-fomit-frame-pointer"
242                };
243                push_if_supported(cc_flag.into());
244            }
245            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mno-red-zone
246            // https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mno-red-zone
247            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mred-zone
248            // https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mred-zone
249            if let Some(value) = self.no_redzone {
250                let cc_flag = if value { "-mno-red-zone" } else { "-mred-zone" };
251                push_if_supported(cc_flag.into());
252            }
253            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-msoft-float
254            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mhard-float
255            // https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html (several archs, search for `-msoft-float`).
256            // https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html (several archs, search for `-mhard-float`).
257            if let Some(value) = self.soft_float {
258                let cc_flag = if value {
259                    "-msoft-float"
260                } else {
261                    // Do not use -mno-soft-float, that's basically just an alias for -mno-implicit-float.
262                    "-mhard-float"
263                };
264                push_if_supported(cc_flag.into());
265            }
266            // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-gdwarf-2
267            // https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html#index-gdwarf
268            if let Some(value) = self.dwarf_version {
269                push_if_supported(format!("-gdwarf-{value}").into());
270            }
271        }
272
273        // Compiler-exclusive flags
274        match family {
275            ToolFamily::Clang { .. } => {
276                // GNU and Clang compilers both support the same PGO flags, but they use different libraries and
277                // different formats for the profile files which are not compatible.
278                // clang and rustc both internally use llvm, so we want to inherit the PGO flags only for clang.
279                // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fprofile-generate
280                if let Some(value) = self.profile_generate {
281                    push_if_supported(format!("-fprofile-generate={value}").into());
282                }
283                // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fprofile-use
284                if let Some(value) = self.profile_use {
285                    push_if_supported(format!("-fprofile-use={value}").into());
286                }
287
288                // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fembed-bitcode
289                if let Some(value) = self.embed_bitcode {
290                    let cc_val = if value { "all" } else { "off" };
291                    push_if_supported(format!("-fembed-bitcode={cc_val}").into());
292                }
293
294                // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-flto
295                if let Some(value) = self.lto {
296                    let cc_val = match value {
297                        "y" | "yes" | "on" | "true" | "fat" => Some("full"),
298                        "thin" => Some("thin"),
299                        _ => None,
300                    };
301                    if let Some(cc_val) = cc_val {
302                        push_if_supported(format!("-flto={cc_val}").into());
303                    }
304                }
305                // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mguard
306                if let Some(value) = self.control_flow_guard {
307                    let cc_val = match value {
308                        "y" | "yes" | "on" | "true" | "checks" => Some("cf"),
309                        "nochecks" => Some("cf-nochecks"),
310                        "n" | "no" | "off" | "false" => Some("none"),
311                        _ => None,
312                    };
313                    if let Some(cc_val) = cc_val {
314                        push_if_supported(format!("-mguard={cc_val}").into());
315                    }
316                }
317            }
318            ToolFamily::Gnu => {}
319            ToolFamily::Msvc { .. } => {
320                // https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-control-flow-guard
321                if let Some(value) = self.control_flow_guard {
322                    let cc_val = match value {
323                        "y" | "yes" | "on" | "true" | "checks" => Some("cf"),
324                        "n" | "no" | "off" | "false" => Some("cf-"),
325                        _ => None,
326                    };
327                    if let Some(cc_val) = cc_val {
328                        push_if_supported(format!("/guard:{cc_val}").into());
329                    }
330                }
331                // https://learn.microsoft.com/en-us/cpp/build/reference/oy-frame-pointer-omission
332                if let Some(value) = self.force_frame_pointers {
333                    // Flag is unsupported on 64-bit arches
334                    if !target.arch.contains("64") {
335                        let cc_flag = if value { "/Oy-" } else { "/Oy" };
336                        push_if_supported(cc_flag.into());
337                    }
338                }
339            }
340        }
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[track_caller]
349    fn check(env: &str, expected: &RustcCodegenFlags) {
350        let actual = RustcCodegenFlags::parse(env).unwrap();
351        assert_eq!(actual, *expected);
352    }
353
354    #[test]
355    fn codegen_type() {
356        let expected = RustcCodegenFlags {
357            code_model: Some("tiny"),
358            ..RustcCodegenFlags::default()
359        };
360        check("-Ccode-model=tiny", &expected);
361        check("-C\u{1f}code-model=tiny", &expected);
362        check("--codegen\u{1f}code-model=tiny", &expected);
363        check("--codegen=code-model=tiny", &expected);
364    }
365
366    #[test]
367    fn precedence() {
368        check(
369            "-ccode-model=tiny\u{1f}-Ccode-model=small",
370            &RustcCodegenFlags {
371                code_model: Some("small"),
372                ..RustcCodegenFlags::default()
373            },
374        );
375    }
376
377    #[test]
378    fn two_valid_prefixes() {
379        let expected = RustcCodegenFlags::default();
380        check("-L\u{1f}-Clto", &expected);
381    }
382
383    #[test]
384    fn three_valid_prefixes() {
385        let expected = RustcCodegenFlags {
386            lto: Some("true"),
387            ..RustcCodegenFlags::default()
388        };
389        check("-L\u{1f}-L\u{1f}-Clto", &expected);
390    }
391
392    #[test]
393    fn all_rustc_flags() {
394        // Throw all possible flags at the parser to catch false positives
395        let flags = [
396            // Set all the flags we recognise first
397            "-Ccode-model=tiny",
398            "-Ccontrol-flow-guard=yes",
399            "-Cembed-bitcode=no",
400            "-Cforce-frame-pointers=yes",
401            "-Clto=false",
402            "-Clink-dead-code=yes",
403            "-Cno-redzone=yes",
404            "-Cno-vectorize-loops",
405            "-Cno-vectorize-slp",
406            "-Cprofile-generate=fooprofile",
407            "-Cprofile-use=fooprofile",
408            "-Crelocation-model=pic",
409            "-Csoft-float=yes",
410            "-Zbranch-protection=bti,pac-ret,leaf",
411            "-Zdwarf-version=5",
412            // Set flags we don't recognise but rustc supports next
413            // rustc flags
414            "--cfg",
415            "a",
416            "--check-cfg 'cfg(verbose)",
417            "-L",
418            "/usr/lib/foo",
419            "-l",
420            "static:+whole-archive=mylib",
421            "--crate-type=dylib",
422            "--crate-name=foo",
423            "--edition=2021",
424            "--emit=asm",
425            "--print=crate-name",
426            "-g",
427            "-O",
428            "-o",
429            "foooutput",
430            "--out-dir",
431            "foooutdir",
432            "--target",
433            "aarch64-unknown-linux-gnu",
434            "-W",
435            "missing-docs",
436            "-D",
437            "unused-variables",
438            "--force-warn",
439            "dead-code",
440            "-A",
441            "unused",
442            "-F",
443            "unused",
444            "--cap-lints",
445            "warn",
446            "--version",
447            "--verbose",
448            "-v",
449            "--extern",
450            "foocrate",
451            "--sysroot",
452            "fooroot",
453            "--error-format",
454            "human",
455            "--color",
456            "auto",
457            "--diagnostic-width",
458            "80",
459            "--remap-path-prefix",
460            "foo=bar",
461            "--json=artifact",
462            // Codegen flags
463            "-Car",
464            "-Ccodegen-units=1",
465            "-Ccollapse-macro-debuginfo=yes",
466            "-Cdebug-assertions=yes",
467            "-Cdebuginfo=1",
468            "-Cdefault-linker-libraries=yes",
469            "-Cdlltool=foo",
470            "-Cextra-filename=foo",
471            "-Cforce-unwind-tables=yes",
472            "-Cincremental=foodir",
473            "-Cinline-threshold=6",
474            "-Cinstrument-coverage",
475            "-Clink-arg=-foo",
476            "-Clink-args=-foo",
477            "-Clink-self-contained=yes",
478            "-Clinker=lld",
479            "-Clinker-flavor=ld.lld",
480            "-Clinker-plugin-lto=yes",
481            "-Cllvm-args=foo",
482            "-Cmetadata=foo",
483            "-Cno-prepopulate-passes",
484            "-Cno-stack-check",
485            "-Copt-level=3",
486            "-Coverflow-checks=yes",
487            "-Cpanic=abort",
488            "-Cpasses=foopass",
489            "-Cprefer-dynamic=yes",
490            "-Crelro-level=partial",
491            "-Cremark=all",
492            "-Crpath=yes",
493            "-Csave-temps=yes",
494            "-Csplit-debuginfo=packed",
495            "-Cstrip=symbols",
496            "-Csymbol-mangling-version=v0",
497            "-Ctarget-cpu=native",
498            "-Ctarget-feature=+sve",
499            // Unstable options
500            "-Ztune-cpu=machine",
501        ];
502        check(
503            &flags.join("\u{1f}"),
504            &RustcCodegenFlags {
505                code_model: Some("tiny"),
506                control_flow_guard: Some("yes"),
507                embed_bitcode: Some(false),
508                force_frame_pointers: Some(true),
509                lto: Some("false"),
510                no_redzone: Some(true),
511                no_vectorize_loops: true,
512                no_vectorize_slp: true,
513                profile_generate: Some("fooprofile"),
514                profile_use: Some("fooprofile"),
515                relocation_model: Some("pic"),
516                soft_float: Some(true),
517                branch_protection: Some("bti,pac-ret,leaf"),
518                dwarf_version: Some(5),
519            },
520        );
521    }
522}