From e76efbcea41d9fee1a4be5688dcb6224977db21c Mon Sep 17 00:00:00 2001 From: Kajetan Puchalski Date: Fri, 1 Nov 2024 16:29:48 +0000 Subject: [PATCH] build: Inherit flags from rustc Where applicable, detect which RUSTFLAGS were set for rustc and convert them into their corresponding cc flags in order to ensure consistent codegen across Rust and non-Rust modules. --- src/lib.rs | 321 +++++++++++++++++++++++++++++++++++++++++++++ tests/rustflags.rs | 29 ++++ 2 files changed, 350 insertions(+) create mode 100644 tests/rustflags.rs diff --git a/src/lib.rs b/src/lib.rs index 45faecd3..89f5b3de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -312,6 +312,7 @@ pub struct Build { emit_rerun_if_env_changed: bool, cached_compiler_family: Arc, ToolFamily>>>, shell_escaped_flags: Option, + inherit_rustflags: bool, } /// Represents the types of errors that may occur while using cc-rs. @@ -437,6 +438,7 @@ impl Build { emit_rerun_if_env_changed: true, cached_compiler_family: Arc::default(), shell_escaped_flags: None, + inherit_rustflags: true, } } @@ -664,6 +666,7 @@ impl Build { .debug(false) .cpp(self.cpp) .cuda(self.cuda) + .inherit_rustflags(false) .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed); if let Some(target) = &self.target { cfg.target(target); @@ -1313,6 +1316,15 @@ impl Build { self } + /// Configure whether cc should automatically inherit compatible flags passed to rustc + /// from `CARGO_ENCODED_RUSTFLAGS`. + /// + /// This option defaults to `true`. + pub fn inherit_rustflags(&mut self, inherit_rustflags: bool) -> &mut Build { + self.inherit_rustflags = inherit_rustflags; + self + } + #[doc(hidden)] pub fn __set_env(&mut self, a: A, b: B) -> &mut Build where @@ -1904,6 +1916,11 @@ impl Build { cmd.args.push((**flag).into()); } + // Add cc flags inherited from matching rustc flags + if self.inherit_rustflags { + self.add_inherited_rustflags(&mut cmd, &target)?; + } + for flag in self.flags_supported.iter() { if self .is_flag_supported_inner(flag, &cmd.path, &target) @@ -2439,6 +2456,25 @@ impl Build { Ok(()) } + fn add_inherited_rustflags(&self, cmd: &mut Tool, target: &Target) -> Result<(), Error> { + let env_os = match self.getenv("CARGO_ENCODED_RUSTFLAGS") { + Some(env) => env, + // No encoded RUSTFLAGS -> nothing to do + None => return Ok(()), + }; + + let Tool { + family, path, args, .. + } = cmd; + + let env = env_os.to_string_lossy(); + let codegen_flags = RustcCodegenFlags::parse(&env); + println!("flags: {:?}", codegen_flags); + args.extend(codegen_flags.cc_flags(&self, path, family, target)); + println!("ccflags: {:?}", args); + Ok(()) + } + fn has_flags(&self) -> bool { let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" }; let flags_env_var_value = self.getenv_with_target_prefixes(flags_env_var_name); @@ -4221,6 +4257,291 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &Target) -> &str } } +#[derive(Debug)] +struct RustcCodegenFlags { + branch_protection: Option, + code_model: Option, + no_vectorize_loops: bool, + no_vectorize_slp: bool, + profile_generate: Option, + profile_use: Option, + control_flow_guard: Option, + lto: Option, + relocation_model: Option, + embed_bitcode: Option, + force_frame_pointers: Option, + link_dead_code: Option, + no_redzone: Option, + soft_float: Option, +} + +impl RustcCodegenFlags { + // Parse flags obtained from CARGO_ENCODED_RUSTFLAGS + fn parse(rustflags_env: &str) -> RustcCodegenFlags { + fn is_flag_prefix(flag: &str) -> bool { + match flag { + "-Z" | "-C" | "--codegen" => true, + _ => false, + } + } + + fn join_flag_prefix<'a>(prev: &'a str, curr: &'a str) -> Cow<'a, str> { + match prev { + "--codegen" | "-C" => Cow::from(format!("-C{}", curr)), + "-Z" => Cow::from(format!("-Z{}", curr)), + _ => Cow::from(curr), + } + } + + let mut codegen_flags = RustcCodegenFlags { + branch_protection: None, + code_model: None, + no_vectorize_loops: false, + no_vectorize_slp: false, + profile_generate: None, + profile_use: None, + control_flow_guard: None, + lto: None, + relocation_model: None, + embed_bitcode: None, + force_frame_pointers: None, + link_dead_code: None, + no_redzone: None, + soft_float: None, + }; + + let env_flags: Vec<&str> = rustflags_env.split("\u{1f}").collect(); + + if !is_flag_prefix(env_flags[0]) { + codegen_flags.set_rustc_flag(env_flags[0]); + } + + for i in 1..env_flags.len() { + let curr = env_flags[i]; + let prev = env_flags[i - 1]; + + // Do not process prefixes on their own + if !is_flag_prefix(curr) { + // Concat flags preceded by a prefix + let rustc_flag = join_flag_prefix(prev, curr); + codegen_flags.set_rustc_flag(&rustc_flag); + } + } + + codegen_flags + } + + fn set_rustc_flag(&mut self, flag: &str) { + if flag.starts_with("-Z") { + self.set_unstable_rustc_flag(flag); + } else { + self.set_stable_rustc_flag(flag); + } + } + + fn set_stable_rustc_flag(&mut self, flag: &str) { + let (flag, value) = if let Some((flag, value)) = flag.split_once('=') { + (flag, Some(value.to_owned())) + } else { + (flag, None) + }; + + match flag { + "-Ccode-model" => self.code_model = value, + "-Cno-vectorize-loops" => self.no_vectorize_loops = true, + "-Cno-vectorize-slp" => self.no_vectorize_slp = true, + "-Cprofile-generate" => self.profile_generate = value, + "-Cprofile-use" => self.profile_use = value, + "-Ccontrol-flow-guard" => self.control_flow_guard = value.or(Some("true".into())), + "-Clto" => self.lto = value.or(Some("true".into())), + "-Crelocation-model" => self.relocation_model = value, + "-Cembed-bitcode" => { + self.embed_bitcode = value.map_or(Some(true), |val| match val.as_str() { + "y" | "yes" | "on" | "true" => Some(true), + "n" | "no" | "off" | "false" => Some(false), + _ => None, + }); + } + "-Cforce-frame-pointers" => { + self.force_frame_pointers = value.map_or(Some(true), |val| match val.as_str() { + "y" | "yes" | "on" | "true" => Some(true), + "n" | "no" | "off" | "false" => Some(false), + _ => None, + }); + } + "-Clink-dead-code" => { + self.link_dead_code = value.map_or(Some(true), |val| match val.as_str() { + "y" | "yes" | "on" | "true" => Some(true), + "n" | "no" | "off" | "false" => Some(false), + _ => None, + }) + } + "-Cno-redzone" => { + self.no_redzone = value.map_or(Some(true), |val| match val.as_str() { + "y" | "yes" | "on" | "true" => Some(true), + "n" | "no" | "off" | "false" => Some(false), + _ => None, + }); + } + "-Csoft-float" => { + self.soft_float = value.map_or(Some(true), |val| match val.as_str() { + "y" | "yes" | "on" | "true" => Some(true), + "n" | "no" | "off" | "false" => Some(false), + _ => None, + }); + } + _ => {} + } + } + + fn set_unstable_rustc_flag(&mut self, flag: &str) { + let (flag, value) = if let Some((flag, value)) = flag.split_once('=') { + (flag, Some(value.to_owned())) + } else { + (flag, None) + }; + + match flag { + "-Zbranch-protection" => self.branch_protection = value, + _ => {} + } + } + + // Rust and clang/cc don't agree on what equivalent flags should look like either. + fn cc_flags( + &self, + build: &Build, + path: &PathBuf, + family: &ToolFamily, + target: &Target, + ) -> Vec { + let push_if_supported = |flags: &mut Vec, flag: OsString| { + if build + .is_flag_supported_inner(&flag, path, target) + .unwrap_or(false) + { + flags.push(flag); + } else { + build.cargo_output.print_warning(&format!( + "Inherited flag {:?} is not supported by the currently used CC", + flag + )); + } + }; + + let mut flags: Vec = vec![]; + + match family { + ToolFamily::Clang { .. } | ToolFamily::Gnu => { + if let Some(value) = &self.branch_protection { + push_if_supported( + &mut flags, + format!("-mbranch-protection={}", value.replace(",", "+")).into(), + ); + } + if let Some(value) = &self.code_model { + push_if_supported(&mut flags, format!("-mcmodel={value}").into()); + } + if self.no_vectorize_loops { + push_if_supported(&mut flags, "-fno-vectorize".into()); + } + if self.no_vectorize_slp { + push_if_supported(&mut flags, "-fno-slp-vectorize".into()); + } + if let Some(value) = &self.profile_generate { + push_if_supported(&mut flags, format!("-fprofile-generate={value}").into()); + } + if let Some(value) = &self.profile_use { + push_if_supported(&mut flags, format!("-fprofile-use={value}").into()); + } + if let Some(value) = &self.control_flow_guard { + let cc_val = match value.as_str() { + "y" | "yes" | "on" | "true" | "checks" => Some("cf"), + "nochecks" => Some("cf-nochecks"), + "n" | "no" | "off" | "false" => Some("none"), + _ => None, + }; + if let Some(cc_val) = cc_val { + push_if_supported(&mut flags, format!("-mguard={cc_val}").into()); + } + } + if let Some(value) = &self.lto { + let cc_val = match value.as_str() { + "y" | "yes" | "on" | "true" | "fat" => Some("full"), + "thin" => Some("thin"), + _ => None, + }; + if let Some(cc_val) = cc_val { + push_if_supported(&mut flags, format!("-flto={cc_val}").into()); + } + } + if let Some(value) = &self.relocation_model { + let cc_flag = match value.as_str() { + "pic" => Some("-fPIC"), + "pie" => Some("-fPIE"), + "dynamic-no-pic" => Some("-mdynamic-no-pic"), + _ => None, + }; + if let Some(cc_flag) = cc_flag { + push_if_supported(&mut flags, cc_flag.into()); + } + } + if let Some(value) = &self.embed_bitcode { + let cc_val = if *value { "all" } else { "off" }; + push_if_supported(&mut flags, format!("-fembed-bitcode={cc_val}").into()); + } + if let Some(value) = &self.force_frame_pointers { + let cc_flag = if *value { + "-fno-omit-frame-pointer" + } else { + "-fomit-frame-pointer" + }; + push_if_supported(&mut flags, cc_flag.into()); + } + if let Some(value) = &self.link_dead_code { + if !value { + push_if_supported(&mut flags, "-dead_strip".into()); + } + } + if let Some(value) = &self.no_redzone { + let cc_flag = if *value { + "-mno-red-zone" + } else { + "-mred-zone" + }; + push_if_supported(&mut flags, cc_flag.into()); + } + if let Some(value) = &self.soft_float { + let cc_flag = if *value { + "-msoft-float" + } else { + "-mno-soft-float" + }; + push_if_supported(&mut flags, cc_flag.into()); + } + } + ToolFamily::Msvc { .. } => { + if let Some(value) = &self.control_flow_guard { + let cc_val = match value.as_str() { + "y" | "yes" | "on" | "true" | "checks" => Some("cf"), + "n" | "no" | "off" | "false" => Some("cf-"), + _ => None, + }; + if let Some(cc_val) = cc_val { + push_if_supported(&mut flags, format!("/guard:{cc_val}").into()); + } + } + if let Some(value) = &self.force_frame_pointers { + let cc_flag = if *value { "/Oy-" } else { "/Oy" }; + push_if_supported(&mut flags, cc_flag.into()); + } + } + } + + flags + } +} + #[derive(Clone, Copy, PartialEq)] enum AsmFileExt { /// `.asm` files. On MSVC targets, we assume these should be passed to MASM diff --git a/tests/rustflags.rs b/tests/rustflags.rs new file mode 100644 index 00000000..c9c6fc14 --- /dev/null +++ b/tests/rustflags.rs @@ -0,0 +1,29 @@ +use crate::support::Test; +mod support; + +/// This test is in its own module because it modifies the environment and would affect other tests +/// when run in parallel with them. +#[test] +#[cfg(not(windows))] +fn inherits_rustflags() { + // Sanity check - no flags + std::env::set_var("CARGO_ENCODED_RUSTFLAGS", ""); + let test = Test::gnu(); + test.gcc().file("foo.c").compile("foo"); + test.cmd(0) + .must_not_have("-fno-omit-frame-pointer") + .must_not_have("-mcmodel=small") + .must_not_have("-msoft-float"); + + // Correctly inherits flags from rustc + std::env::set_var( + "CARGO_ENCODED_RUSTFLAGS", + "-Cforce-frame-pointers=true\u{1f}-Ccode-model=small\u{1f}-Csoft-float", + ); + let test = Test::gnu(); + test.gcc().file("foo.c").compile("foo"); + test.cmd(0) + .must_have("-fno-omit-frame-pointer") + .must_have("-mcmodel=small") + .must_have("-msoft-float"); +}