explicitly model that certain ABIs require/forbid certain target features
This commit is contained in:
parent
41b579660c
commit
2bf27e09be
18 changed files with 304 additions and 273 deletions
|
@ -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}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue