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 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 _ if curr.starts_with("--codegen=") => ("-C", &curr[10..]),
52 "-Z" => ("-Z", curr),
53 "-L" | "-l" | "-o" => (prev, curr),
54 "-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 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 "-Ccode-model" => {
115 self.code_model = Some(flag_ok_or(value, "-Ccode-model must have a value")?);
116 }
117 "-Cno-vectorize-loops" => self.no_vectorize_loops = true,
119 "-Cno-vectorize-slp" => self.no_vectorize_slp = true,
121 "-Cprofile-generate" => {
123 self.profile_generate =
124 Some(flag_ok_or(value, "-Cprofile-generate must have a value")?);
125 }
126 "-Cprofile-use" => {
128 self.profile_use = Some(flag_ok_or(value, "-Cprofile-use must have a value")?);
129 }
130 "-Ccontrol-flow-guard" => self.control_flow_guard = value.or(Some("true")),
132 "-Clto" => self.lto = value.or(Some("true")),
134 "-Crelocation-model" => {
136 self.relocation_model =
137 Some(flag_ok_or(value, "-Crelocation-model must have a value")?);
138 }
139 "-Cembed-bitcode" => self.embed_bitcode = value.map_or(Some(true), arg_to_bool),
141 "-Cforce-frame-pointers" => {
143 self.force_frame_pointers = value.map_or(Some(true), arg_to_bool)
144 }
145 "-Cno-redzone" => self.no_redzone = value.map_or(Some(true), arg_to_bool),
147 "-Csoft-float" => self.soft_float = value.map_or(Some(true), arg_to_bool),
150 "-Zbranch-protection" | "-Cbranch-protection" => {
153 self.branch_protection =
154 Some(flag_ok_or(value, "-Zbranch-protection must have a value")?);
155 }
156 "-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 pub(crate) fn cc_flags(&self, build: &Build, tool: &mut Tool, target: &TargetInfo<'_>) {
171 let family = tool.family;
172 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 {flag:?} is not supported by the currently used CC"
182 ));
183 }
184 };
185
186 let clang_or_gnu =
187 matches!(family, ToolFamily::Clang { .. }) || matches!(family, ToolFamily::Gnu);
188
189 if clang_or_gnu {
191 if let Some(value) = self.branch_protection {
196 push_if_supported(
197 format!("-mbranch-protection={}", value.replace(",", "+")).into(),
198 );
199 }
200 if let Some(value) = self.code_model {
204 push_if_supported(format!("-mcmodel={value}").into());
205 }
206 if self.no_vectorize_loops {
209 push_if_supported("-fno-vectorize".into());
210 }
211 if self.no_vectorize_slp {
214 push_if_supported("-fno-slp-vectorize".into());
215 }
216 if let Some(value) = self.relocation_model {
217 let cc_flag = match value {
218 "pic" => Some("-fPIC"),
221 "pie" => Some("-fPIE"),
224 "dynamic-no-pic" => Some("-mdynamic-no-pic"),
227 _ => None,
228 };
229 if let Some(cc_flag) = cc_flag {
230 push_if_supported(cc_flag.into());
231 }
232 }
233 if let Some(value) = self.force_frame_pointers {
237 let cc_flag = if value {
238 "-fno-omit-frame-pointer"
239 } else {
240 "-fomit-frame-pointer"
241 };
242 push_if_supported(cc_flag.into());
243 }
244 if let Some(value) = self.no_redzone {
249 let cc_flag = if value { "-mno-red-zone" } else { "-mred-zone" };
250 push_if_supported(cc_flag.into());
251 }
252 if let Some(value) = self.soft_float {
257 let cc_flag = if value {
258 "-msoft-float"
259 } else {
260 "-mhard-float"
262 };
263 push_if_supported(cc_flag.into());
264 }
265 if let Some(value) = self.dwarf_version {
268 push_if_supported(format!("-gdwarf-{value}").into());
269 }
270 }
271
272 match family {
274 ToolFamily::Clang { .. } => {
275 if let Some(value) = self.profile_generate {
280 push_if_supported(format!("-fprofile-generate={value}").into());
281 }
282 if let Some(value) = self.profile_use {
284 push_if_supported(format!("-fprofile-use={value}").into());
285 }
286
287 if let Some(value) = self.embed_bitcode {
289 let cc_val = if value { "all" } else { "off" };
290 push_if_supported(format!("-fembed-bitcode={cc_val}").into());
291 }
292
293 if let Some(value) = self.lto {
295 let cc_val = match value {
296 "y" | "yes" | "on" | "true" | "fat" => Some("full"),
297 "thin" => Some("thin"),
298 _ => None,
299 };
300 if let Some(cc_val) = cc_val {
301 push_if_supported(format!("-flto={cc_val}").into());
302 }
303 }
304 if let Some(value) = self.control_flow_guard {
306 let cc_val = match value {
307 "y" | "yes" | "on" | "true" | "checks" => Some("cf"),
308 "nochecks" => Some("cf-nochecks"),
309 "n" | "no" | "off" | "false" => Some("none"),
310 _ => None,
311 };
312 if let Some(cc_val) = cc_val {
313 push_if_supported(format!("-mguard={cc_val}").into());
314 }
315 }
316 }
317 ToolFamily::Gnu => {}
318 ToolFamily::Msvc { .. } => {
319 if let Some(value) = self.control_flow_guard {
321 let cc_val = match value {
322 "y" | "yes" | "on" | "true" | "checks" => Some("cf"),
323 "n" | "no" | "off" | "false" => Some("cf-"),
324 _ => None,
325 };
326 if let Some(cc_val) = cc_val {
327 push_if_supported(format!("/guard:{cc_val}").into());
328 }
329 }
330 if let Some(value) = self.force_frame_pointers {
332 if !target.arch.contains("64") {
334 let cc_flag = if value { "/Oy-" } else { "/Oy" };
335 push_if_supported(cc_flag.into());
336 }
337 }
338 }
339 }
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[track_caller]
348 fn check(env: &str, expected: &RustcCodegenFlags) {
349 let actual = RustcCodegenFlags::parse(env).unwrap();
350 assert_eq!(actual, *expected);
351 }
352
353 #[test]
354 fn codegen_type() {
355 let expected = RustcCodegenFlags {
356 code_model: Some("tiny"),
357 ..RustcCodegenFlags::default()
358 };
359 check("-Ccode-model=tiny", &expected);
360 check("-C\u{1f}code-model=tiny", &expected);
361 check("--codegen\u{1f}code-model=tiny", &expected);
362 check("--codegen=code-model=tiny", &expected);
363 }
364
365 #[test]
366 fn precedence() {
367 check(
368 "-ccode-model=tiny\u{1f}-Ccode-model=small",
369 &RustcCodegenFlags {
370 code_model: Some("small"),
371 ..RustcCodegenFlags::default()
372 },
373 );
374 }
375
376 #[test]
377 fn two_valid_prefixes() {
378 let expected = RustcCodegenFlags::default();
379 check("-L\u{1f}-Clto", &expected);
380 }
381
382 #[test]
383 fn three_valid_prefixes() {
384 let expected = RustcCodegenFlags {
385 lto: Some("true"),
386 ..RustcCodegenFlags::default()
387 };
388 check("-L\u{1f}-L\u{1f}-Clto", &expected);
389 }
390
391 #[test]
392 fn all_rustc_flags() {
393 let flags = [
395 "-Ccode-model=tiny",
397 "-Ccontrol-flow-guard=yes",
398 "-Cembed-bitcode=no",
399 "-Cforce-frame-pointers=yes",
400 "-Clto=false",
401 "-Clink-dead-code=yes",
402 "-Cno-redzone=yes",
403 "-Cno-vectorize-loops",
404 "-Cno-vectorize-slp",
405 "-Cprofile-generate=fooprofile",
406 "-Cprofile-use=fooprofile",
407 "-Crelocation-model=pic",
408 "-Csoft-float=yes",
409 "-Zbranch-protection=bti,pac-ret,leaf",
410 "-Zdwarf-version=5",
411 "--cfg",
414 "a",
415 "--check-cfg 'cfg(verbose)",
416 "-L",
417 "/usr/lib/foo",
418 "-l",
419 "static:+whole-archive=mylib",
420 "--crate-type=dylib",
421 "--crate-name=foo",
422 "--edition=2021",
423 "--emit=asm",
424 "--print=crate-name",
425 "-g",
426 "-O",
427 "-o",
428 "foooutput",
429 "--out-dir",
430 "foooutdir",
431 "--target",
432 "aarch64-unknown-linux-gnu",
433 "-W",
434 "missing-docs",
435 "-D",
436 "unused-variables",
437 "--force-warn",
438 "dead-code",
439 "-A",
440 "unused",
441 "-F",
442 "unused",
443 "--cap-lints",
444 "warn",
445 "--version",
446 "--verbose",
447 "-v",
448 "--extern",
449 "foocrate",
450 "--sysroot",
451 "fooroot",
452 "--error-format",
453 "human",
454 "--color",
455 "auto",
456 "--diagnostic-width",
457 "80",
458 "--remap-path-prefix",
459 "foo=bar",
460 "--json=artifact",
461 "-Car",
463 "-Ccodegen-units=1",
464 "-Ccollapse-macro-debuginfo=yes",
465 "-Cdebug-assertions=yes",
466 "-Cdebuginfo=1",
467 "-Cdefault-linker-libraries=yes",
468 "-Cdlltool=foo",
469 "-Cextra-filename=foo",
470 "-Cforce-unwind-tables=yes",
471 "-Cincremental=foodir",
472 "-Cinline-threshold=6",
473 "-Cinstrument-coverage",
474 "-Clink-arg=-foo",
475 "-Clink-args=-foo",
476 "-Clink-self-contained=yes",
477 "-Clinker=lld",
478 "-Clinker-flavor=ld.lld",
479 "-Clinker-plugin-lto=yes",
480 "-Cllvm-args=foo",
481 "-Cmetadata=foo",
482 "-Cno-prepopulate-passes",
483 "-Cno-stack-check",
484 "-Copt-level=3",
485 "-Coverflow-checks=yes",
486 "-Cpanic=abort",
487 "-Cpasses=foopass",
488 "-Cprefer-dynamic=yes",
489 "-Crelro-level=partial",
490 "-Cremark=all",
491 "-Crpath=yes",
492 "-Csave-temps=yes",
493 "-Csplit-debuginfo=packed",
494 "-Cstrip=symbols",
495 "-Csymbol-mangling-version=v0",
496 "-Ctarget-cpu=native",
497 "-Ctarget-feature=+sve",
498 "-Ztune-cpu=machine",
500 ];
501 check(
502 &flags.join("\u{1f}"),
503 &RustcCodegenFlags {
504 code_model: Some("tiny"),
505 control_flow_guard: Some("yes"),
506 embed_bitcode: Some(false),
507 force_frame_pointers: Some(true),
508 lto: Some("false"),
509 no_redzone: Some(true),
510 no_vectorize_loops: true,
511 no_vectorize_slp: true,
512 profile_generate: Some("fooprofile"),
513 profile_use: Some("fooprofile"),
514 relocation_model: Some("pic"),
515 soft_float: Some(true),
516 branch_protection: Some("bti,pac-ret,leaf"),
517 dwarf_version: Some(5),
518 },
519 );
520 }
521}