explicitly model that certain ABIs require/forbid certain target features

This commit is contained in:
Ralf Jung 2024-12-26 18:32:22 +01:00
parent 41b579660c
commit 2bf27e09be
18 changed files with 304 additions and 273 deletions

View file

@ -8,7 +8,7 @@ codegen_llvm_dynamic_linking_with_lto =
codegen_llvm_fixed_x18_invalid_arch = the `-Zfixed-x18` flag is not supported on the `{$arch}` architecture
codegen_llvm_forbidden_ctarget_feature =
target feature `{$feature}` cannot be toggled with `-Ctarget-feature`: {$reason}
target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}
.note = this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
codegen_llvm_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
@ -22,8 +22,6 @@ codegen_llvm_invalid_minimum_alignment_not_power_of_two =
codegen_llvm_invalid_minimum_alignment_too_large =
invalid minimum global alignment: {$align} is too large
codegen_llvm_invalid_target_feature_prefix = target feature `{$feature}` must begin with a `+` or `-`"
codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}"
codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err}

View file

@ -37,6 +37,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
#[note(codegen_llvm_forbidden_ctarget_feature_issue)]
pub(crate) struct ForbiddenCTargetFeature<'a> {
pub feature: &'a str,
pub enabled: &'a str,
pub reason: &'a str,
}
@ -205,12 +206,6 @@ pub(crate) struct MismatchedDataLayout<'a> {
pub llvm_layout: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_llvm_invalid_target_feature_prefix)]
pub(crate) struct InvalidTargetFeaturePrefix<'a> {
pub feature: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_llvm_fixed_x18_invalid_arch)]
pub(crate) struct FixedX18InvalidArch<'a> {

View file

@ -21,8 +21,8 @@ use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATU
use crate::back::write::create_informational_target_machine;
use crate::errors::{
FixedX18InvalidArch, ForbiddenCTargetFeature, InvalidTargetFeaturePrefix, PossibleFeature,
UnknownCTargetFeature, UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
FixedX18InvalidArch, ForbiddenCTargetFeature, PossibleFeature, UnknownCTargetFeature,
UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
};
use crate::llvm;
@ -348,7 +348,16 @@ pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol>
{
if enabled {
// Also add all transitively implied features.
features.extend(sess.target.implied_target_features(std::iter::once(feature)));
// We don't care about the order in `features` since the only thing we use it for is the
// `features.contains` below.
#[allow(rustc::potential_query_instability)]
features.extend(
sess.target
.implied_target_features(std::iter::once(feature.as_str()))
.iter()
.map(|s| Symbol::intern(s)),
);
} else {
// Remove transitively reverse-implied features.
@ -356,7 +365,11 @@ pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol>
// `features.contains` below.
#[allow(rustc::potential_query_instability)]
features.retain(|f| {
if sess.target.implied_target_features(std::iter::once(*f)).contains(&feature) {
if sess
.target
.implied_target_features(std::iter::once(f.as_str()))
.contains(&feature.as_str())
{
// If `f` if implies `feature`, then `!feature` implies `!f`, so we have to
// remove `f`. (This is the standard logical contraposition principle.)
false
@ -638,7 +651,7 @@ pub(crate) fn global_llvm_features(
sess.target
.features
.split(',')
.filter(|v| !v.is_empty() && backend_feature_name(sess, v).is_some())
.filter(|v| !v.is_empty())
// Drop +v8plus feature introduced in LLVM 20.
.filter(|v| *v != "+v8plus" || get_version() >= (20, 0, 0))
.map(String::from),
@ -651,89 +664,126 @@ pub(crate) fn global_llvm_features(
// -Ctarget-features
if !only_base_features {
let known_features = sess.target.rust_target_features();
// Will only be filled when `diagnostics` is set!
let mut featsmap = FxHashMap::default();
// insert implied features
// Ensure that all ABI-required features are enabled, and the ABI-forbidden ones
// are disabled.
let (abi_enable, abi_disable) = sess.target.abi_required_features();
let abi_enable_set = FxHashSet::from_iter(abi_enable.iter().copied());
let abi_disable_set = FxHashSet::from_iter(abi_disable.iter().copied());
// Compute implied features
let mut all_rust_features = vec![];
for feature in sess.opts.cg.target_feature.split(',') {
match feature.strip_prefix('+') {
Some(feature) => all_rust_features.extend(
UnordSet::from(
sess.target
.implied_target_features(std::iter::once(Symbol::intern(feature))),
)
.to_sorted_stable_ord()
.iter()
.map(|s| format!("+{}", s.as_str())),
),
_ => all_rust_features.push(feature.to_string()),
if let Some(feature) = feature.strip_prefix('+') {
all_rust_features.extend(
UnordSet::from(sess.target.implied_target_features(std::iter::once(feature)))
.to_sorted_stable_ord()
.iter()
.map(|&&s| (true, s)),
)
} else if let Some(feature) = feature.strip_prefix('-') {
// FIXME: Why do we not remove implied features on "-" here?
// We do the equivalent above in `target_features_cfg`.
// See <https://github.com/rust-lang/rust/issues/134792>.
all_rust_features.push((false, feature));
} else if !feature.is_empty() {
if diagnostics {
sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature });
}
}
}
// Remove features that are meant for rustc, not LLVM.
all_rust_features.retain(|(_, feature)| {
// Retain if it is not a rustc feature
!RUSTC_SPECIFIC_FEATURES.contains(feature)
});
// Check feature validity.
if diagnostics {
for &(enable, feature) in &all_rust_features {
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
match feature_state {
None => {
let rust_feature =
known_features.iter().find_map(|&(rust_feature, _, _)| {
let llvm_features = to_llvm_features(sess, rust_feature)?;
if llvm_features.contains(feature)
&& !llvm_features.contains(rust_feature)
{
Some(rust_feature)
} else {
None
}
});
let unknown_feature = if let Some(rust_feature) = rust_feature {
UnknownCTargetFeature {
feature,
rust_feature: PossibleFeature::Some { rust_feature },
}
} else {
UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
};
sess.dcx().emit_warn(unknown_feature);
}
Some((_, stability, _)) => {
if let Err(reason) = stability.toggle_allowed(&sess.target, enable) {
sess.dcx().emit_warn(ForbiddenCTargetFeature {
feature,
enabled: if enable { "enabled" } else { "disabled" },
reason,
});
} else if stability.requires_nightly().is_some() {
// An unstable feature. Warn about using it. It makes little sense
// to hard-error here since we just warn about fully unknown
// features above.
sess.dcx().emit_warn(UnstableCTargetFeature { feature });
}
}
}
// Ensure that the features we enable/disable are compatible with the ABI.
if enable {
if abi_disable_set.contains(feature) {
sess.dcx().emit_warn(ForbiddenCTargetFeature {
feature,
enabled: "enabled",
reason: "this feature is incompatible with the target ABI",
});
}
} else {
if abi_enable_set.contains(feature) {
sess.dcx().emit_warn(ForbiddenCTargetFeature {
feature,
enabled: "disabled",
reason: "this feature is required by the target ABI",
});
}
}
// FIXME(nagisa): figure out how to not allocate a full hashset here.
featsmap.insert(feature, enable);
}
}
// To be sure the ABI-relevant features are all in the right state, we explicitly
// (un)set them here. This means if the target spec sets those features wrong,
// we will silently correct them rather than silently producing wrong code.
// (The target sanity check tries to catch this, but we can't know which features are
// enabled in LLVM by default so we can't be fully sure about that check.)
for feature in abi_enable {
all_rust_features.push((true, feature));
}
for feature in abi_disable {
all_rust_features.push((false, feature));
}
// Translate this into LLVM features.
let feats = all_rust_features
.iter()
.filter_map(|s| {
let enable_disable = match s.chars().next() {
None => return None,
Some(c @ ('+' | '-')) => c,
Some(_) => {
if diagnostics {
sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature: s });
}
return None;
}
};
// Get the backend feature name, if any.
// This excludes rustc-specific features, which do not get passed to LLVM.
let feature = backend_feature_name(sess, s)?;
// Warn against use of LLVM specific feature names and unstable features on the CLI.
if diagnostics {
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
match feature_state {
None => {
let rust_feature =
known_features.iter().find_map(|&(rust_feature, _, _)| {
let llvm_features = to_llvm_features(sess, rust_feature)?;
if llvm_features.contains(feature)
&& !llvm_features.contains(rust_feature)
{
Some(rust_feature)
} else {
None
}
});
let unknown_feature = if let Some(rust_feature) = rust_feature {
UnknownCTargetFeature {
feature,
rust_feature: PossibleFeature::Some { rust_feature },
}
} else {
UnknownCTargetFeature {
feature,
rust_feature: PossibleFeature::None,
}
};
sess.dcx().emit_warn(unknown_feature);
}
Some((_, stability, _)) => {
if let Err(reason) =
stability.toggle_allowed(&sess.target, enable_disable == '+')
{
sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
} else if stability.requires_nightly().is_some() {
// An unstable feature. Warn about using it. It makes little sense
// to hard-error here since we just warn about fully unknown
// features above.
sess.dcx().emit_warn(UnstableCTargetFeature { feature });
}
}
}
// FIXME(nagisa): figure out how to not allocate a full hashset here.
featsmap.insert(feature, enable_disable == '+');
}
.filter_map(|&(enable, feature)| {
let enable_disable = if enable { '+' } else { '-' };
// We run through `to_llvm_features` when
// passing requests down to LLVM. This means that all in-language
// features also work on the command line instead of having two
@ -746,9 +796,9 @@ pub(crate) fn global_llvm_features(
enable_disable, llvm_feature.llvm_feature_name
))
.chain(llvm_feature.dependency.into_iter().filter_map(
move |feat| match (enable_disable, feat) {
('-' | '+', TargetFeatureFoldStrength::Both(f))
| ('+', TargetFeatureFoldStrength::EnableOnly(f)) => {
move |feat| match (enable, feat) {
(_, TargetFeatureFoldStrength::Both(f))
| (true, TargetFeatureFoldStrength::EnableOnly(f)) => {
Some(format!("{enable_disable}{f}"))
}
_ => None,
@ -780,22 +830,6 @@ pub(crate) fn global_llvm_features(
features
}
/// Returns a feature name for the given `+feature` or `-feature` string.
///
/// Only allows features that are backend specific (i.e. not [`RUSTC_SPECIFIC_FEATURES`].)
fn backend_feature_name<'a>(sess: &Session, s: &'a str) -> Option<&'a str> {
// features must start with a `+` or `-`.
let feature = s
.strip_prefix(&['+', '-'][..])
.unwrap_or_else(|| sess.dcx().emit_fatal(InvalidTargetFeaturePrefix { feature: s }));
// Rustc-specific feature requests like `+crt-static` or `-crt-static`
// are not passed down to LLVM.
if s.is_empty() || RUSTC_SPECIFIC_FEATURES.contains(&feature) {
return None;
}
Some(feature)
}
pub(crate) fn tune_cpu(sess: &Session) -> Option<&str> {
let name = sess.opts.unstable_opts.tune_cpu.as_ref()?;
Some(handle_native(name))