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 {:?} 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 if clang_or_gnu {
192 if let Some(value) = self.branch_protection {
197 push_if_supported(
198 format!("-mbranch-protection={}", value.replace(",", "+")).into(),
199 );
200 }
201 if let Some(value) = self.code_model {
205 push_if_supported(format!("-mcmodel={value}").into());
206 }
207 if self.no_vectorize_loops {
210 push_if_supported("-fno-vectorize".into());
211 }
212 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 "pic" => Some("-fPIC"),
222 "pie" => Some("-fPIE"),
225 "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 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 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 if let Some(value) = self.soft_float {
258 let cc_flag = if value {
259 "-msoft-float"
260 } else {
261 "-mhard-float"
263 };
264 push_if_supported(cc_flag.into());
265 }
266 if let Some(value) = self.dwarf_version {
269 push_if_supported(format!("-gdwarf-{value}").into());
270 }
271 }
272
273 match family {
275 ToolFamily::Clang { .. } => {
276 if let Some(value) = self.profile_generate {
281 push_if_supported(format!("-fprofile-generate={value}").into());
282 }
283 if let Some(value) = self.profile_use {
285 push_if_supported(format!("-fprofile-use={value}").into());
286 }
287
288 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 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 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 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 if let Some(value) = self.force_frame_pointers {
333 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 let flags = [
396 "-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 "--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 "-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 "-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}