Skip to content

Commit

Permalink
rustc_codegen_llvm: Handle unavailable LLVM features
Browse files Browse the repository at this point in the history
Some codegen features may or may not be available depending on the LLVM
version. Rewrite the way features are mapped and passed down to LLVM to
be able to handle those properly.
  • Loading branch information
mrkajetanp committed Jul 25, 2024
1 parent e3a5705 commit b829f3c
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 71 deletions.
5 changes: 2 additions & 3 deletions compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,8 @@ pub fn from_fn_attrs<'ll, 'tcx>(

let function_features = function_features
.iter()
.flat_map(|feat| {
llvm_util::to_llvm_features(cx.tcx.sess, feat).into_iter().map(|f| format!("+{f}"))
})
.flat_map(|feat| llvm_util::to_llvm_features(cx.tcx.sess, feat))
.map(|f| format!("+{f}"))
.chain(codegen_fn_attrs.instruction_set.iter().map(|x| match x {
InstructionSetAttr::ArmA32 => "-thumb-mode".to_string(),
InstructionSetAttr::ArmT32 => "+thumb-mode".to_string(),
Expand Down
161 changes: 93 additions & 68 deletions compiler/rustc_codegen_llvm/src/llvm_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use rustc_target::spec::{MergeFunctions, PanicStrategy};
use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES};

use std::ffi::{c_char, c_void, CStr, CString};
use std::fmt;
use std::fmt::Write;
use std::path::Path;
use std::ptr;
Expand Down Expand Up @@ -197,6 +198,12 @@ impl<'a> IntoIterator for LLVMFeature<'a> {
}
}

impl<'a> fmt::Display for LLVMFeature<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.llvm_feature_name)
}
}

// WARNING: the features after applying `to_llvm_features` must be known
// to LLVM or the feature detection code will walk past the end of the feature
// array, leading to crashes.
Expand All @@ -209,7 +216,7 @@ impl<'a> IntoIterator for LLVMFeature<'a> {
// Though note that Rust can also be build with an external precompiled version of LLVM
// which might lead to failures if the oldest tested / supported LLVM version
// doesn't yet support the relevant intrinsics
pub fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> LLVMFeature<'a> {
pub fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFeature<'a>> {
let arch = if sess.target.arch == "x86_64" {
"x86"
} else if sess.target.arch == "arm64ec" {
Expand All @@ -218,77 +225,88 @@ pub fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> LLVMFeature<'a> {
&*sess.target.arch
};
match (arch, s) {
("x86", "sse4.2") => {
LLVMFeature::with_dependency("sse4.2", TargetFeatureFoldStrength::EnableOnly("crc32"))
}
("x86", "pclmulqdq") => LLVMFeature::new("pclmul"),
("x86", "rdrand") => LLVMFeature::new("rdrnd"),
("x86", "bmi1") => LLVMFeature::new("bmi"),
("x86", "cmpxchg16b") => LLVMFeature::new("cx16"),
("x86", "lahfsahf") => LLVMFeature::new("sahf"),
("aarch64", "rcpc2") => LLVMFeature::new("rcpc-immo"),
("aarch64", "dpb") => LLVMFeature::new("ccpp"),
("aarch64", "dpb2") => LLVMFeature::new("ccdp"),
("aarch64", "frintts") => LLVMFeature::new("fptoint"),
("aarch64", "fcma") => LLVMFeature::new("complxnum"),
("aarch64", "pmuv3") => LLVMFeature::new("perfmon"),
("aarch64", "paca") => LLVMFeature::new("pauth"),
("aarch64", "pacg") => LLVMFeature::new("pauth"),
("aarch64", "sve-b16b16") => LLVMFeature::new("b16b16"),
("aarch64", "flagm2") => LLVMFeature::new("altnzcv"),
("x86", "sse4.2") => Some(LLVMFeature::with_dependency(
"sse4.2",
TargetFeatureFoldStrength::EnableOnly("crc32"),
)),
("x86", "pclmulqdq") => Some(LLVMFeature::new("pclmul")),
("x86", "rdrand") => Some(LLVMFeature::new("rdrnd")),
("x86", "bmi1") => Some(LLVMFeature::new("bmi")),
("x86", "cmpxchg16b") => Some(LLVMFeature::new("cx16")),
("x86", "lahfsahf") => Some(LLVMFeature::new("sahf")),
("aarch64", "rcpc2") => Some(LLVMFeature::new("rcpc-immo")),
("aarch64", "dpb") => Some(LLVMFeature::new("ccpp")),
("aarch64", "dpb2") => Some(LLVMFeature::new("ccdp")),
("aarch64", "frintts") => Some(LLVMFeature::new("fptoint")),
("aarch64", "fcma") => Some(LLVMFeature::new("complxnum")),
("aarch64", "pmuv3") => Some(LLVMFeature::new("perfmon")),
("aarch64", "paca") => Some(LLVMFeature::new("pauth")),
("aarch64", "pacg") => Some(LLVMFeature::new("pauth")),
("aarch64", "sve-b16b16") => Some(LLVMFeature::new("b16b16")),
("aarch64", "flagm2") => Some(LLVMFeature::new("altnzcv")),
// Rust ties fp and neon together.
("aarch64", "neon") => {
LLVMFeature::with_dependency("neon", TargetFeatureFoldStrength::Both("fp-armv8"))
Some(LLVMFeature::with_dependency("neon", TargetFeatureFoldStrength::Both("fp-armv8")))
}
// In LLVM neon implicitly enables fp, but we manually enable
// neon when a feature only implicitly enables fp
("aarch64", "f32mm") => {
LLVMFeature::with_dependency("f32mm", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "f64mm") => {
LLVMFeature::with_dependency("f64mm", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "fhm") => {
LLVMFeature::with_dependency("fp16fml", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "fp16") => {
LLVMFeature::with_dependency("fullfp16", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "jsconv") => {
LLVMFeature::with_dependency("jsconv", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "f32mm") => Some(LLVMFeature::with_dependency(
"f32mm",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "f64mm") => Some(LLVMFeature::with_dependency(
"f64mm",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "fhm") => Some(LLVMFeature::with_dependency(
"fp16fml",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "fp16") => Some(LLVMFeature::with_dependency(
"fullfp16",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "jsconv") => Some(LLVMFeature::with_dependency(
"jsconv",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve") => {
LLVMFeature::with_dependency("sve", TargetFeatureFoldStrength::EnableOnly("neon"))
Some(LLVMFeature::with_dependency("sve", TargetFeatureFoldStrength::EnableOnly("neon")))
}
("aarch64", "sve2") => {
LLVMFeature::with_dependency("sve2", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "sve2p1") => {
LLVMFeature::with_dependency("sve2p1", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "sve2-aes") => {
LLVMFeature::with_dependency("sve2-aes", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "sve2-sm4") => {
LLVMFeature::with_dependency("sve2-sm4", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "sve2-sha3") => {
LLVMFeature::with_dependency("sve2-sha3", TargetFeatureFoldStrength::EnableOnly("neon"))
}
("aarch64", "sve2-bitperm") => LLVMFeature::with_dependency(
("aarch64", "sve2") => Some(LLVMFeature::with_dependency(
"sve2",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve2p1") => Some(LLVMFeature::with_dependency(
"sve2p1",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve2-aes") => Some(LLVMFeature::with_dependency(
"sve2-aes",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve2-sm4") => Some(LLVMFeature::with_dependency(
"sve2-sm4",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve2-sha3") => Some(LLVMFeature::with_dependency(
"sve2-sha3",
TargetFeatureFoldStrength::EnableOnly("neon"),
)),
("aarch64", "sve2-bitperm") => Some(LLVMFeature::with_dependency(
"sve2-bitperm",
TargetFeatureFoldStrength::EnableOnly("neon"),
),
)),
// In LLVM 18, `unaligned-scalar-mem` was merged with `unaligned-vector-mem` into a single feature called
// `fast-unaligned-access`. In LLVM 19, it was split back out.
("riscv32" | "riscv64", "unaligned-scalar-mem") if get_version().0 == 18 => {
LLVMFeature::new("fast-unaligned-access")
Some(LLVMFeature::new("fast-unaligned-access"))
}
// For LLVM 18, enable the evex512 target feature if a avx512 target feature is enabled.
("x86", s) if get_version().0 >= 18 && s.starts_with("avx512") => {
LLVMFeature::with_dependency(s, TargetFeatureFoldStrength::EnableOnly("evex512"))
Some(LLVMFeature::with_dependency(s, TargetFeatureFoldStrength::EnableOnly("evex512")))
}
(_, s) => LLVMFeature::new(s),
(_, s) => Some(LLVMFeature::new(s)),
}
}

Expand Down Expand Up @@ -331,13 +349,17 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
return true;
}
// check that all features in a given smallvec are enabled
for llvm_feature in to_llvm_features(sess, feature) {
let cstr = SmallCStr::new(llvm_feature);
if !unsafe { llvm::LLVMRustHasFeature(&target_machine, cstr.as_ptr()) } {
return false;
if let Some(llvm_features) = to_llvm_features(sess, feature) {
for llvm_feature in llvm_features {
let cstr = SmallCStr::new(llvm_feature);
if !unsafe { llvm::LLVMRustHasFeature(&target_machine, cstr.as_ptr()) } {
return false;
}
}
true
} else {
false
}
true
})
.map(|feature| Symbol::intern(feature))
.collect()
Expand Down Expand Up @@ -388,13 +410,13 @@ fn llvm_target_features(tm: &llvm::TargetMachine) -> Vec<(&str, &str)> {
fn print_target_features(out: &mut String, sess: &Session, tm: &llvm::TargetMachine) {
let mut llvm_target_features = llvm_target_features(tm);
let mut known_llvm_target_features = FxHashSet::<&'static str>::default();
let mut rustc_target_features = sess
let mut rustc_target_features: Vec<(&str, &str)> = sess
.target
.supported_target_features()
.iter()
.map(|(feature, _gate)| {
.filter_map(|(feature, _gate)| {
// LLVM asserts that these are sorted. LLVM and Rust both use byte comparison for these strings.
let llvm_feature = to_llvm_features(sess, *feature).llvm_feature_name;
let llvm_feature = to_llvm_features(sess, *feature)?.llvm_feature_name;
let desc =
match llvm_target_features.binary_search_by_key(&llvm_feature, |(f, _d)| f).ok() {
Some(index) => {
Expand All @@ -404,9 +426,9 @@ fn print_target_features(out: &mut String, sess: &Session, tm: &llvm::TargetMach
None => "",
};

(*feature, desc)
Some((*feature, desc))
})
.collect::<Vec<_>>();
.collect();

// Since we add this at the end ...
rustc_target_features.extend_from_slice(&[(
Expand Down Expand Up @@ -580,7 +602,7 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec<Str
let feature_state = supported_features.iter().find(|&&(v, _)| v == feature);
if feature_state.is_none() {
let rust_feature = supported_features.iter().find_map(|&(rust_feature, _)| {
let llvm_features = to_llvm_features(sess, rust_feature);
let llvm_features = to_llvm_features(sess, rust_feature)?;
if llvm_features.contains(feature) && !llvm_features.contains(rust_feature)
{
Some(rust_feature)
Expand Down Expand Up @@ -625,7 +647,7 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec<Str
// passing requests down to LLVM. This means that all in-language
// features also work on the command line instead of having two
// different names when the LLVM name and the Rust name differ.
let llvm_feature = to_llvm_features(sess, feature);
let llvm_feature = to_llvm_features(sess, feature)?;

Some(
std::iter::once(format!("{}{}", enable_disable, llvm_feature.llvm_feature_name))
Expand Down Expand Up @@ -671,6 +693,9 @@ fn backend_feature_name<'a>(sess: &Session, s: &'a str) -> Option<&'a str> {
let feature = s
.strip_prefix(&['+', '-'][..])
.unwrap_or_else(|| sess.dcx().emit_fatal(InvalidTargetFeaturePrefix { feature: s }));
if s.is_empty() {
return None;
}
// Rustc-specific feature requests like `+crt-static` or `-crt-static`
// are not passed down to LLVM.
if RUSTC_SPECIFIC_FEATURES.contains(&feature) {
Expand Down

0 comments on commit b829f3c

Please sign in to comment.