move -Ctarget-feature handling into shared code

This commit is contained in:
Ralf Jung 2025-05-23 08:07:42 +02:00
parent d70ec32ea7
commit cd08652faa
18 changed files with 324 additions and 460 deletions

View file

@ -1,7 +1,3 @@
codegen_gcc_unknown_ctarget_feature_prefix =
unknown feature specified for `-Ctarget-feature`: `{$feature}`
.note = features must begin with a `+` to enable or `-` to disable it
codegen_gcc_unwinding_inline_asm =
GCC backend does not support unwinding from inline asm
@ -16,15 +12,3 @@ codegen_gcc_lto_disallowed = lto can only be run for executables, cdylibs and st
codegen_gcc_lto_dylib = lto cannot be used for `dylib` crate type without `-Zdylib-lto`
codegen_gcc_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$gcc_err})
codegen_gcc_unknown_ctarget_feature =
unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
.note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
.possible_feature = you might have meant: `{$rust_feature}`
.consider_filing_feature_request = consider filing a feature request
codegen_gcc_missing_features =
add the missing features in a `target_feature` attribute
codegen_gcc_target_feature_disable_or_enable =
the target features {$features} must all be either enabled or disabled together

View file

@ -1,30 +1,6 @@
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_macros::Diagnostic;
use rustc_span::Span;
#[derive(Diagnostic)]
#[diag(codegen_gcc_unknown_ctarget_feature_prefix)]
#[note]
pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
pub feature: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_gcc_unknown_ctarget_feature)]
#[note]
pub(crate) struct UnknownCTargetFeature<'a> {
pub feature: &'a str,
#[subdiagnostic]
pub rust_feature: PossibleFeature<'a>,
}
#[derive(Subdiagnostic)]
pub(crate) enum PossibleFeature<'a> {
#[help(codegen_gcc_possible_feature)]
Some { rust_feature: &'a str },
#[help(codegen_gcc_consider_filing_feature_request)]
None,
}
#[derive(Diagnostic)]
#[diag(codegen_gcc_unwinding_inline_asm)]
pub(crate) struct UnwindingInlineAsm {

View file

@ -1,20 +1,11 @@
#[cfg(feature = "master")]
use gccjit::Context;
use rustc_codegen_ssa::codegen_attrs::check_tied_features;
use rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unord::UnordSet;
use rustc_codegen_ssa::target_features;
use rustc_session::Session;
use rustc_session::features::{StabilityExt, retpoline_features_by_flags};
use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
use smallvec::{SmallVec, smallvec};
use crate::errors::{PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix};
fn gcc_features_by_flags(sess: &Session) -> Vec<&str> {
let mut features: Vec<&str> = Vec::new();
retpoline_features_by_flags(sess, &mut features);
features
fn gcc_features_by_flags(sess: &Session, features: &mut Vec<String>) {
target_features::retpoline_features_by_flags(sess, features);
}
/// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@ -44,98 +35,31 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
features.extend(sess.target.features.split(',').filter(|v| !v.is_empty()).map(String::from));
// -Ctarget-features
let known_features = sess.target.rust_target_features();
let mut featsmap = FxHashMap::default();
// Compute implied features
let mut all_rust_features = vec![];
for feature in sess.opts.cg.target_feature.split(',').chain(gcc_features_by_flags(sess)) {
if let Some(feature) = feature.strip_prefix('+') {
all_rust_features.extend(
UnordSet::from(sess.target.implied_target_features(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_config`.
// See <https://github.com/rust-lang/rust/issues/134792>.
all_rust_features.push((false, feature));
} else if !feature.is_empty() && diagnostics {
sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature });
}
}
// Remove features that are meant for rustc, not codegen.
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 gcc_features = to_gcc_features(sess, rust_feature);
if gcc_features.contains(&feature) && !gcc_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, _)) => {
stability.verify_feature_enabled_by_flag(sess, enable, feature);
}
}
// FIXME(nagisa): figure out how to not allocate a full hashset here.
featsmap.insert(feature, enable);
}
}
// Translate this into GCC features.
let feats =
all_rust_features.iter().flat_map(|&(enable, feature)| {
let enable_disable = if enable { '+' } else { '-' };
target_features::flag_to_backend_features(
sess,
diagnostics,
|feature| to_gcc_features(sess, feature),
|feature, enable| {
// We run through `to_gcc_features` when
// passing requests down to GCC. This means that all in-language
// features also work on the command line instead of having two
// different names when the GCC name and the Rust name differ.
to_gcc_features(sess, feature)
.iter()
.flat_map(|feat| to_gcc_features(sess, feat).into_iter())
.map(|feature| {
if enable_disable == '-' {
format!("-{}", feature)
} else {
feature.to_string()
}
})
.collect::<Vec<_>>()
});
features.extend(feats);
features.extend(
to_gcc_features(sess, feature)
.iter()
.flat_map(|feat| to_gcc_features(sess, feat).into_iter())
.map(
|feature| {
if !enable { format!("-{}", feature) } else { feature.to_string() }
},
),
);
},
);
if diagnostics && let Some(f) = check_tied_features(sess, &featsmap) {
sess.dcx().emit_err(TargetFeatureDisableOrEnable {
features: f,
span: None,
missing_features: None,
});
}
gcc_features_by_flags(sess, &mut features);
// FIXME: LLVM also sets +reserve-x18 here under some conditions.
features
}

View file

@ -59,16 +59,6 @@ codegen_llvm_symbol_already_defined =
codegen_llvm_target_machine = could not create LLVM TargetMachine for triple: {$triple}
codegen_llvm_target_machine_with_llvm_err = could not create LLVM TargetMachine for triple: {$triple}: {$llvm_err}
codegen_llvm_unknown_ctarget_feature =
unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
.note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
.possible_feature = you might have meant: `{$rust_feature}`
.consider_filing_feature_request = consider filing a feature request
codegen_llvm_unknown_ctarget_feature_prefix =
unknown feature specified for `-Ctarget-feature`: `{$feature}`
.note = features must begin with a `+` to enable or `-` to disable it
codegen_llvm_unknown_debuginfo_compression = unknown debuginfo compression algorithm {$algorithm} - will fall back to uncompressed debuginfo
codegen_llvm_write_bytecode = failed to write bytecode to {$path}: {$err}

View file

@ -3,35 +3,11 @@ use std::path::Path;
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_macros::Diagnostic;
use rustc_span::Span;
use crate::fluent_generated as fluent;
#[derive(Diagnostic)]
#[diag(codegen_llvm_unknown_ctarget_feature_prefix)]
#[note]
pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
pub feature: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_llvm_unknown_ctarget_feature)]
#[note]
pub(crate) struct UnknownCTargetFeature<'a> {
pub feature: &'a str,
#[subdiagnostic]
pub rust_feature: PossibleFeature<'a>,
}
#[derive(Subdiagnostic)]
pub(crate) enum PossibleFeature<'a> {
#[help(codegen_llvm_possible_feature)]
Some { rust_feature: &'a str },
#[help(codegen_llvm_consider_filing_feature_request)]
None,
}
#[derive(Diagnostic)]
#[diag(codegen_llvm_symbol_already_defined)]
pub(crate) struct SymbolAlreadyDefined<'a> {

View file

@ -7,25 +7,18 @@ use std::{ptr, slice, str};
use libc::c_int;
use rustc_codegen_ssa::base::wants_wasm_eh;
use rustc_codegen_ssa::codegen_attrs::check_tied_features;
use rustc_codegen_ssa::{TargetConfig, target_features};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_data_structures::unord::UnordSet;
use rustc_fs_util::path_to_c_string;
use rustc_middle::bug;
use rustc_session::Session;
use rustc_session::config::{PrintKind, PrintRequest};
use rustc_session::features::{StabilityExt, retpoline_features_by_flags};
use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport};
use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
use smallvec::{SmallVec, smallvec};
use crate::back::write::create_informational_target_machine;
use crate::errors::{
FixedX18InvalidArch, PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix,
};
use crate::llvm;
use crate::{errors, llvm};
static INIT: Once = Once::new();
@ -194,15 +187,6 @@ impl<'a> LLVMFeature<'a> {
) -> Self {
Self { llvm_feature_name, dependencies }
}
fn contains(&'a self, feat: &str) -> bool {
self.iter().any(|dep| dep == feat)
}
fn iter(&'a self) -> impl Iterator<Item = &'a str> {
let dependencies = self.dependencies.iter().map(|feat| feat.as_str());
std::iter::once(self.llvm_feature_name).chain(dependencies)
}
}
impl<'a> IntoIterator for LLVMFeature<'a> {
@ -215,18 +199,22 @@ impl<'a> IntoIterator for LLVMFeature<'a> {
}
}
// 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.
//
// To find a list of LLVM's names, see llvm-project/llvm/lib/Target/{ARCH}/*.td
// where `{ARCH}` is the architecture name. Look for instances of `SubtargetFeature`.
//
// Check the current rustc fork of LLVM in the repo at https://github.com/rust-lang/llvm-project/.
// The commit in use can be found via the `llvm-project` submodule in
// https://github.com/rust-lang/rust/tree/master/src 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.
/// Convert a Rust feature name to an LLVM feature name. Returning `None` means the
/// feature should be skipped, usually because it is not supported by the current
/// LLVM version.
///
/// 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.
///
/// To find a list of LLVM's names, see llvm-project/llvm/lib/Target/{ARCH}/*.td
/// where `{ARCH}` is the architecture name. Look for instances of `SubtargetFeature`.
///
/// Check the current rustc fork of LLVM in the repo at <https://github.com/rust-lang/llvm-project/>.
/// The commit in use can be found via the `llvm-project` submodule in
/// <https://github.com/rust-lang/rust/tree/master/src> 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(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFeature<'a>> {
let arch = if sess.target.arch == "x86_64" {
"x86"
@ -634,10 +622,9 @@ pub(crate) fn target_cpu(sess: &Session) -> &str {
handle_native(cpu_name)
}
fn llvm_features_by_flags(sess: &Session) -> Vec<&str> {
let mut features: Vec<&str> = Vec::new();
retpoline_features_by_flags(sess, &mut features);
features
/// The target features for compiler flags other than `-Ctarget-features`.
fn llvm_features_by_flags(sess: &Session, features: &mut Vec<String>) {
target_features::retpoline_features_by_flags(sess, features);
}
/// The list of LLVM features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@ -704,6 +691,8 @@ pub(crate) fn global_llvm_features(
.split(',')
.filter(|v| !v.is_empty())
// Drop +v8plus feature introduced in LLVM 20.
// (Hard-coded target features do not go through `to_llvm_feature` since they already
// are LLVM feature names, hence we need a special case here.)
.filter(|v| *v != "+v8plus" || get_version() >= (20, 0, 0))
.map(String::from),
);
@ -714,86 +703,23 @@ 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();
// Compute implied features
let mut all_rust_features = vec![];
for feature in sess.opts.cg.target_feature.split(',').chain(llvm_features_by_flags(sess)) {
if let Some(feature) = feature.strip_prefix('+') {
all_rust_features.extend(
UnordSet::from(sess.target.implied_target_features(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_config`.
// 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, _)) => {
stability.verify_feature_enabled_by_flag(sess, enable, feature);
}
}
// FIXME(nagisa): figure out how to not allocate a full hashset here.
featsmap.insert(feature, enable);
}
}
// Translate this into LLVM features.
let feats = all_rust_features
.iter()
.filter_map(|&(enable, feature)| {
target_features::flag_to_backend_features(
sess,
diagnostics,
|feature| {
to_llvm_features(sess, feature)
.map(|f| SmallVec::<[&str; 2]>::from_iter(f.into_iter()))
.unwrap_or_default()
},
|feature, enable| {
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
// different names when the LLVM name and the Rust name differ.
let llvm_feature = to_llvm_features(sess, feature)?;
let Some(llvm_feature) = to_llvm_features(sess, feature) else { return };
Some(
features.extend(
std::iter::once(format!(
"{}{}",
enable_disable, llvm_feature.llvm_feature_name
@ -808,23 +734,17 @@ pub(crate) fn global_llvm_features(
},
)),
)
})
.flatten();
features.extend(feats);
},
);
if diagnostics && let Some(f) = check_tied_features(sess, &featsmap) {
sess.dcx().emit_err(rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable {
features: f,
span: None,
missing_features: None,
});
}
llvm_features_by_flags(sess, &mut features);
}
// -Zfixed-x18
// FIXME: merge with `llvm_features_by_flags`.
if sess.opts.unstable_opts.fixed_x18 {
if sess.target.arch != "aarch64" {
sess.dcx().emit_fatal(FixedX18InvalidArch { arch: &sess.target.arch });
sess.dcx().emit_fatal(errors::FixedX18InvalidArch { arch: &sess.target.arch });
} else {
features.push("+reserve-x18".into());
}

View file

@ -68,6 +68,11 @@ codegen_ssa_failed_to_write = failed to write {$path}: {$error}
codegen_ssa_field_associated_value_expected = associated value expected for `{$name}`
codegen_ssa_forbidden_ctarget_feature =
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_ssa_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
codegen_ssa_forbidden_target_feature_attr =
target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}
@ -368,8 +373,22 @@ codegen_ssa_unexpected_parameter_name = unexpected parameter name
codegen_ssa_unknown_archive_kind =
Don't know how to build archive of type: {$kind}
codegen_ssa_unknown_ctarget_feature =
unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
.note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
.possible_feature = you might have meant: `{$rust_feature}`
.consider_filing_feature_request = consider filing a feature request
codegen_ssa_unknown_ctarget_feature_prefix =
unknown feature specified for `-Ctarget-feature`: `{$feature}`
.note = features must begin with a `+` to enable or `-` to disable it
codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified
codegen_ssa_unstable_ctarget_feature =
unstable feature specified for `-Ctarget-feature`: `{$feature}`
.note = this feature is not stably supported; its behavior can change in the future
codegen_ssa_unsupported_instruction_set = target does not support `#[instruction_set]`
codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target

View file

@ -7,7 +7,6 @@ use rustc_attr_data_structures::ReprAttr::ReprAlign;
use rustc_attr_data_structures::{
AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, find_attr,
};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
@ -19,13 +18,15 @@ use rustc_middle::mir::mono::Linkage;
use rustc_middle::query::Providers;
use rustc_middle::span_bug;
use rustc_middle::ty::{self as ty, TyCtxt};
use rustc_session::lint;
use rustc_session::parse::feature_err;
use rustc_session::{Session, lint};
use rustc_span::{Ident, Span, sym};
use rustc_target::spec::SanitizerSet;
use crate::errors;
use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr};
use crate::target_features::{
check_target_feature_trait_unsafe, check_tied_features, from_target_feature_attr,
};
fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
use rustc_middle::mir::mono::Linkage::*;
@ -625,25 +626,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
codegen_fn_attrs
}
/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
/// combinations are allowed.
pub fn check_tied_features(
sess: &Session,
features: &FxHashMap<&str, bool>,
) -> Option<&'static [&'static str]> {
if !features.is_empty() {
for tied in sess.target.tied_target_features() {
// Tied features must be set to the same value, or not set at all
let mut tied_iter = tied.iter();
let enabled = features.get(tied_iter.next().unwrap());
if tied_iter.any(|f| enabled != features.get(f)) {
return Some(tied);
}
}
}
None
}
/// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
/// applied to the method prototype.
fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {

View file

@ -1217,30 +1217,6 @@ pub(crate) struct ErrorCreatingImportLibrary<'a> {
pub error: String,
}
pub struct TargetFeatureDisableOrEnable<'a> {
pub features: &'a [&'a str],
pub span: Option<Span>,
pub missing_features: Option<MissingFeatures>,
}
#[derive(Subdiagnostic)]
#[help(codegen_ssa_missing_features)]
pub struct MissingFeatures;
impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_> {
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
let mut diag = Diag::new(dcx, level, fluent::codegen_ssa_target_feature_disable_or_enable);
if let Some(span) = self.span {
diag.span(span);
};
if let Some(missing_features) = self.missing_features {
diag.subdiagnostic(missing_features);
}
diag.arg("features", self.features.join(", "));
diag
}
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_aix_strip_not_used)]
pub(crate) struct AixStripNotUsed;
@ -1283,3 +1259,68 @@ pub(crate) struct XcrunSdkPathWarning {
#[derive(LintDiagnostic)]
#[diag(codegen_ssa_aarch64_softfloat_neon)]
pub(crate) struct Aarch64SoftfloatNeon;
#[derive(Diagnostic)]
#[diag(codegen_ssa_unknown_ctarget_feature_prefix)]
#[note]
pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
pub feature: &'a str,
}
#[derive(Subdiagnostic)]
pub(crate) enum PossibleFeature<'a> {
#[help(codegen_ssa_possible_feature)]
Some { rust_feature: &'a str },
#[help(codegen_ssa_consider_filing_feature_request)]
None,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_unknown_ctarget_feature)]
#[note]
pub(crate) struct UnknownCTargetFeature<'a> {
pub feature: &'a str,
#[subdiagnostic]
pub rust_feature: PossibleFeature<'a>,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_unstable_ctarget_feature)]
#[note]
pub(crate) struct UnstableCTargetFeature<'a> {
pub feature: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_forbidden_ctarget_feature)]
#[note]
#[note(codegen_ssa_forbidden_ctarget_feature_issue)]
pub(crate) struct ForbiddenCTargetFeature<'a> {
pub feature: &'a str,
pub enabled: &'a str,
pub reason: &'a str,
}
pub struct TargetFeatureDisableOrEnable<'a> {
pub features: &'a [&'a str],
pub span: Option<Span>,
pub missing_features: Option<MissingFeatures>,
}
#[derive(Subdiagnostic)]
#[help(codegen_ssa_missing_features)]
pub struct MissingFeatures;
impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_> {
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
let mut diag = Diag::new(dcx, level, fluent::codegen_ssa_target_feature_disable_or_enable);
if let Some(span) = self.span {
diag.span(span);
};
if let Some(missing_features) = self.missing_features {
diag.subdiagnostic(missing_features);
}
diag.arg("features", self.features.join(", "));
diag
}
}

View file

@ -1,5 +1,5 @@
use rustc_attr_data_structures::InstructionSetAttr;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -9,11 +9,13 @@ use rustc_middle::middle::codegen_fn_attrs::TargetFeature;
use rustc_middle::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
use rustc_session::features::StabilityExt;
use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
use rustc_session::parse::feature_err;
use rustc_span::{Span, Symbol, sym};
use rustc_target::target_features::{self, RUSTC_SPECIAL_FEATURES, Stability};
use rustc_target::target_features::{
self, RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability,
};
use smallvec::SmallVec;
use crate::errors;
@ -68,7 +70,7 @@ pub(crate) fn from_target_feature_attr(
// Only allow target features whose feature gates have been enabled
// and which are permitted to be toggled.
if let Err(reason) = stability.is_toggle_permitted(tcx.sess) {
if let Err(reason) = stability.toggle_allowed() {
tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
span: item.span(),
feature,
@ -89,7 +91,7 @@ pub(crate) fn from_target_feature_attr(
let feature_sym = Symbol::intern(feature);
for &name in tcx.implied_target_features(feature_sym) {
// But ensure the ABI does not forbid enabling this.
// Here we do assume that LLVM doesn't add even more implied features
// Here we do assume that the backend doesn't add even more implied features
// we don't know about, at least no features that would have ABI effects!
// We skip this logic in rustdoc, where we want to allow all target features of
// all targets, so we can't check their ABI compatibility and anyway we are not
@ -251,6 +253,155 @@ pub fn cfg_target_feature(
(f(true), f(false))
}
/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
/// combinations are allowed.
pub fn check_tied_features(
sess: &Session,
features: &FxHashMap<&str, bool>,
) -> Option<&'static [&'static str]> {
if !features.is_empty() {
for tied in sess.target.tied_target_features() {
// Tied features must be set to the same value, or not set at all
let mut tied_iter = tied.iter();
let enabled = features.get(tied_iter.next().unwrap());
if tied_iter.any(|f| enabled != features.get(f)) {
return Some(tied);
}
}
}
None
}
/// Translates the `-Ctarget-feature` flag into a backend target feature list.
///
/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
/// used for diagnostic purposes only.
///
/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
/// accessible by that closure) to enable/disable the given Rust feature name.
pub fn flag_to_backend_features<'a, const N: usize>(
sess: &'a Session,
diagnostics: bool,
to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
) {
let known_features = sess.target.rust_target_features();
// Compute implied features
let mut rust_features = vec![];
for feature in sess.opts.cg.target_feature.split(',') {
if let Some(feature) = feature.strip_prefix('+') {
rust_features.extend(
UnordSet::from(sess.target.implied_target_features(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_config`.
// See <https://github.com/rust-lang/rust/issues/134792>.
rust_features.push((false, feature));
} else if !feature.is_empty() {
if diagnostics {
sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
}
}
}
// Remove features that are meant for rustc, not the backend.
rust_features.retain(|(_, feature)| {
// Retain if it is not a rustc feature
!RUSTC_SPECIFIC_FEATURES.contains(feature)
});
// Check feature validity.
if diagnostics {
let mut featsmap = FxHashMap::default();
for &(enable, feature) in &rust_features {
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
match feature_state {
None => {
// This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
// If so, give a better error message.
let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
let backend_features = to_backend_features(rust_feature);
if backend_features.contains(&feature)
&& !backend_features.contains(&rust_feature)
{
Some(rust_feature)
} else {
None
}
});
let unknown_feature = if let Some(rust_feature) = rust_feature {
errors::UnknownCTargetFeature {
feature,
rust_feature: errors::PossibleFeature::Some { rust_feature },
}
} else {
errors::UnknownCTargetFeature {
feature,
rust_feature: errors::PossibleFeature::None,
}
};
sess.dcx().emit_warn(unknown_feature);
}
Some((_, stability, _)) => {
if let Err(reason) = stability.toggle_allowed() {
sess.dcx().emit_warn(errors::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(errors::UnstableCTargetFeature { feature });
}
}
}
// FIXME(nagisa): figure out how to not allocate a full hashset here.
featsmap.insert(feature, enable);
}
if let Some(f) = check_tied_features(sess, &featsmap) {
sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
features: f,
span: None,
missing_features: None,
});
}
}
// Add this to the backend features.
for (enable, feature) in rust_features {
extend_backend_features(feature, enable);
}
}
/// Computes the backend target features to be added to account for retpoline flags.
/// Used by both LLVM and GCC since their target features are, conveniently, the same.
pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
// -Zretpoline without -Zretpoline-external-thunk enables
// retpoline-indirect-branches and retpoline-indirect-calls target features
let unstable_opts = &sess.opts.unstable_opts;
if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
features.push("+retpoline-indirect-branches".into());
features.push("+retpoline-indirect-calls".into());
}
// -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
// retpoline-external-thunk, retpoline-indirect-branches and
// retpoline-indirect-calls target features
if unstable_opts.retpoline_external_thunk {
features.push("+retpoline-external-thunk".into());
features.push("+retpoline-indirect-branches".into());
features.push("+retpoline-indirect-calls".into());
}
}
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers {
rust_target_features: |tcx, cnum| {

View file

@ -40,11 +40,6 @@ session_file_is_not_writeable = output file {$file} is not writeable -- check it
session_file_write_fail = failed to write `{$path}` due to error `{$err}`
session_forbidden_ctarget_feature =
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!
session_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
session_function_return_requires_x86_or_x86_64 = `-Zfunction-return` (except `keep`) is only supported on x86 and x86_64
session_function_return_thunk_extern_requires_non_large_code_model = `-Zfunction-return=thunk-extern` is only supported on non-large code models
@ -137,9 +132,6 @@ session_target_stack_protector_not_supported = `-Z stack-protector={$stack_prote
session_unleashed_feature_help_named = skipping check for `{$gate}` feature
session_unleashed_feature_help_unnamed = skipping check that does not even have a feature gate
session_unstable_ctarget_feature =
unstable feature specified for `-Ctarget-feature`: `{$feature}`
.note = this feature is not stably supported; its behavior can change in the future
session_unstable_virtual_function_elimination = `-Zvirtual-function-elimination` requires `-Clto`
session_unsupported_crate_type_for_target =

View file

@ -501,20 +501,3 @@ pub(crate) struct SoftFloatIgnored;
#[note]
#[note(session_soft_float_deprecated_issue)]
pub(crate) struct SoftFloatDeprecated;
#[derive(Diagnostic)]
#[diag(session_forbidden_ctarget_feature)]
#[note]
#[note(session_forbidden_ctarget_feature_issue)]
pub(crate) struct ForbiddenCTargetFeature<'a> {
pub feature: &'a str,
pub enabled: &'a str,
pub reason: &'a str,
}
#[derive(Diagnostic)]
#[diag(session_unstable_ctarget_feature)]
#[note]
pub(crate) struct UnstableCTargetFeature<'a> {
pub feature: &'a str,
}

View file

@ -1,59 +0,0 @@
use rustc_target::target_features::Stability;
use crate::Session;
use crate::errors::{ForbiddenCTargetFeature, UnstableCTargetFeature};
pub trait StabilityExt {
/// Returns whether the feature may be toggled via `#[target_feature]` or `-Ctarget-feature`.
/// Otherwise, some features also may only be enabled by flag (target modifier).
/// (It might still be nightly-only even if this returns `true`, so make sure to also check
/// `requires_nightly`.)
fn is_toggle_permitted(&self, sess: &Session) -> Result<(), &'static str>;
/// Check that feature is correctly enabled/disabled by command line flag (emits warnings)
fn verify_feature_enabled_by_flag(&self, sess: &Session, enable: bool, feature: &str);
}
impl StabilityExt for Stability {
fn is_toggle_permitted(&self, sess: &Session) -> Result<(), &'static str> {
match self {
Stability::Forbidden { reason } => Err(reason),
Stability::TargetModifierOnly { reason, flag } => {
if !sess.opts.target_feature_flag_enabled(*flag) { Err(reason) } else { Ok(()) }
}
_ => Ok(()),
}
}
fn verify_feature_enabled_by_flag(&self, sess: &Session, enable: bool, feature: &str) {
if let Err(reason) = self.is_toggle_permitted(sess) {
sess.dcx().emit_warn(ForbiddenCTargetFeature {
feature,
enabled: if enable { "enabled" } else { "disabled" },
reason,
});
} else if self.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 });
}
}
}
pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<&str>) {
// -Zretpoline without -Zretpoline-external-thunk enables
// retpoline-indirect-branches and retpoline-indirect-calls target features
let unstable_opts = &sess.opts.unstable_opts;
if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
features.push("+retpoline-indirect-branches");
features.push("+retpoline-indirect-calls");
}
// -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
// retpoline-external-thunk, retpoline-indirect-branches and
// retpoline-indirect-calls target features
if unstable_opts.retpoline_external_thunk {
features.push("+retpoline-external-thunk");
features.push("+retpoline-indirect-branches");
features.push("+retpoline-indirect-calls");
}
}

View file

@ -29,7 +29,6 @@ pub use session::*;
pub mod output;
pub use getopts;
pub mod features;
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }

View file

@ -290,14 +290,6 @@ macro_rules! top_level_options {
mods.sort_by(|a, b| a.opt.cmp(&b.opt));
mods
}
pub fn target_feature_flag_enabled(&self, flag: &str) -> bool {
match flag {
"retpoline" => self.unstable_opts.retpoline,
"retpoline-external-thunk" => self.unstable_opts.retpoline_external_thunk,
_ => false,
}
}
}
);
}

View file

@ -34,9 +34,6 @@ pub enum Stability {
/// particular for features are actually ABI configuration flags (not all targets are as nice as
/// RISC-V and have an explicit way to set the ABI separate from target features).
Forbidden { reason: &'static str },
/// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be set
/// by target modifier flag. Target modifier flags are tracked to be consistent in linked modules.
TargetModifierOnly { reason: &'static str, flag: &'static str },
}
use Stability::*;
@ -52,7 +49,6 @@ impl<CTX> HashStable<CTX> for Stability {
Stability::Forbidden { reason } => {
reason.hash_stable(hcx, hasher);
}
Stability::TargetModifierOnly { .. } => {}
}
}
}
@ -62,7 +58,7 @@ impl Stability {
/// (It might still be nightly-only even if this returns `true`, so make sure to also check
/// `requires_nightly`.)
pub fn in_cfg(&self) -> bool {
!matches!(self, Stability::Forbidden { .. })
matches!(self, Stability::Stable | Stability::Unstable { .. })
}
/// Returns the nightly feature that is required to toggle this target feature via
@ -78,7 +74,16 @@ impl Stability {
Stability::Unstable(nightly_feature) => Some(nightly_feature),
Stability::Stable { .. } => None,
Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
Stability::TargetModifierOnly { .. } => None,
}
}
/// Returns whether the feature may be toggled via `#[target_feature]` or `-Ctarget-feature`.
/// (It might still be nightly-only even if this returns `true`, so make sure to also check
/// `requires_nightly`.)
pub fn toggle_allowed(&self) -> Result<(), &'static str> {
match self {
Stability::Unstable(_) | Stability::Stable { .. } => Ok(()),
Stability::Forbidden { reason } => Err(reason),
}
}
}
@ -450,26 +455,19 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
("rdseed", Stable, &[]),
(
"retpoline-external-thunk",
Stability::TargetModifierOnly {
Stability::Forbidden {
reason: "use `retpoline-external-thunk` target modifier flag instead",
flag: "retpoline-external-thunk",
},
&[],
),
(
"retpoline-indirect-branches",
Stability::TargetModifierOnly {
reason: "use `retpoline` target modifier flag instead",
flag: "retpoline",
},
Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
&[],
),
(
"retpoline-indirect-calls",
Stability::TargetModifierOnly {
reason: "use `retpoline` target modifier flag instead",
flag: "retpoline",
},
Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
&[],
),
("rtm", Unstable(sym::rtm_target_feature), &[]),

View file

@ -18,7 +18,6 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
use rustc_session::features::StabilityExt;
use rustc_span::def_id::LOCAL_CRATE;
use rustdoc_json_types as types;
// It's important to use the FxHashMap from rustdoc_json_types here, instead of
@ -148,7 +147,7 @@ fn target(sess: &rustc_session::Session) -> types::Target {
.copied()
.filter(|(_, stability, _)| {
// Describe only target features which the user can toggle
stability.is_toggle_permitted(sess).is_ok()
stability.toggle_allowed().is_ok()
})
.map(|(name, stability, implied_features)| {
types::TargetFeature {
@ -164,7 +163,7 @@ fn target(sess: &rustc_session::Session) -> types::Target {
// Imply only target features which the user can toggle
feature_stability
.get(name)
.map(|stability| stability.is_toggle_permitted(sess).is_ok())
.map(|stability| stability.toggle_allowed().is_ok())
.unwrap_or(false)
})
.map(String::from)

View file

@ -212,9 +212,6 @@ LL | cfg!(target_feature = "_UNEXPECTED_VALUE");
`relax`
`relaxed-simd`
`reserve-x18`
`retpoline-external-thunk`
`retpoline-indirect-branches`
`retpoline-indirect-calls`
`rtm`
`sb`
`scq`