Skip to content

Commit

Permalink
build: Inherit flags from rustc
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mrkajetanp committed Nov 18, 2024
1 parent 290a629 commit fc181eb
Show file tree
Hide file tree
Showing 2 changed files with 348 additions and 0 deletions.
319 changes: 319 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ pub struct Build {
emit_rerun_if_env_changed: bool,
cached_compiler_family: Arc<RwLock<HashMap<Box<Path>, ToolFamily>>>,
shell_escaped_flags: Option<bool>,
inherit_rustflags: bool,
}

/// Represents the types of errors that may occur while using cc-rs.
Expand Down Expand Up @@ -437,6 +438,7 @@ impl Build {
emit_rerun_if_env_changed: true,
cached_compiler_family: Arc::default(),
shell_escaped_flags: None,
inherit_rustflags: true,
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<A, B>(&mut self, a: A, b: B) -> &mut Build
where
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -2439,6 +2456,23 @@ 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);
args.extend(codegen_flags.cc_flags(&self, path, family, target));
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);
Expand Down Expand Up @@ -4221,6 +4255,291 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &Target) -> &str
}
}

#[derive(Debug)]
struct RustcCodegenFlags {
branch_protection: Option<String>,
code_model: Option<String>,
no_vectorize_loops: bool,
no_vectorize_slp: bool,
profile_generate: Option<String>,
profile_use: Option<String>,
control_flow_guard: Option<String>,
lto: Option<String>,
relocation_model: Option<String>,
embed_bitcode: Option<bool>,
force_frame_pointers: Option<bool>,
link_dead_code: Option<bool>,
no_redzone: Option<bool>,
soft_float: Option<bool>,
}

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<OsString> {
let push_if_supported = |flags: &mut Vec<OsString>, 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<OsString> = 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
Expand Down
29 changes: 29 additions & 0 deletions tests/rustflags.rs
Original file line number Diff line number Diff line change
@@ -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");
}

0 comments on commit fc181eb

Please sign in to comment.