diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl index 882fff8673a19..6d4e1237a3048 100644 --- a/compiler/rustc_codegen_gcc/messages.ftl +++ b/compiler/rustc_codegen_gcc/messages.ftl @@ -1,13 +1,6 @@ -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_invalid_minimum_alignment = invalid minimum global alignment: {$err} -codegen_gcc_forbidden_ctarget_feature = - target feature `{$feature}` cannot be toggled with `-Ctarget-feature`: {$reason} - codegen_gcc_unwinding_inline_asm = GCC backend does not support unwinding from inline asm @@ -22,19 +15,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_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_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 diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs index 1b59b9ac169ab..42bfdeb797ecc 100644 --- a/compiler/rustc_codegen_gcc/src/errors.rs +++ b/compiler/rustc_codegen_gcc/src/errors.rs @@ -1,45 +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(Diagnostic)] -#[diag(codegen_gcc_unstable_ctarget_feature)] -#[note] -pub(crate) struct UnstableCTargetFeature<'a> { - pub feature: &'a str, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_forbidden_ctarget_feature)] -pub(crate) struct ForbiddenCTargetFeature<'a> { - pub feature: &'a str, - pub enabled: &'a str, - pub reason: &'a str, -} - -#[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 { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 2b053abdd190a..7b7418137491f 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -1,18 +1,9 @@ #[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_middle::target_features; use rustc_session::Session; -use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES; use smallvec::{SmallVec, smallvec}; -use crate::errors::{ - ForbiddenCTargetFeature, PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix, - UnstableCTargetFeature, -}; - /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`, /// `--target` and similar). pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec { @@ -40,109 +31,27 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec. - 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, _)) => { - if let Err(reason) = stability.toggle_allowed() { - 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 }); - } - } - } - - // 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::>() - }); - features.extend(feats); - - if diagnostics && let Some(f) = check_tied_features(sess, &featsmap) { - sess.dcx().emit_err(TargetFeatureDisableOrEnable { - features: f, - span: None, - missing_features: None, - }); - } + 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() } + }, + ), + ); + }, + ); features } diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 2c5a787168381..4874bc8a99191 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -108,6 +108,7 @@ use rustc_data_structures::sync::IntoDynSyncSend; use rustc_errors::DiagCtxtHandle; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; +use rustc_middle::target_features; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; use rustc_session::Session; @@ -486,40 +487,19 @@ fn to_gcc_opt_level(optlevel: Option) -> OptimizationLevel { /// Returns the features that should be set in `cfg(target_feature)`. fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig { - // TODO(antoyo): use global_gcc_features. - let f = |allow_unstable| { - sess.target - .rust_target_features() - .iter() - .filter_map(|&(feature, gate, _)| { - if allow_unstable - || (gate.in_cfg() - && (sess.is_nightly_build() || gate.requires_nightly().is_none())) - { - Some(feature) - } else { - None - } - }) - .filter(|feature| { - // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it. - if *feature == "neon" { - return false; - } - target_info.cpu_supports(feature) - /* - adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma, - avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq, - bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm, - sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves - */ - }) - .map(Symbol::intern) - .collect() - }; - - let target_features = f(false); - let unstable_target_features = f(true); + let (unstable_target_features, target_features) = target_features::cfg(sess, |feature| { + // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it. + if feature == "neon" { + return false; + } + target_info.cpu_supports(feature) + /* + adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma, + avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq, + bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm, + sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves + */ + }); TargetConfig { target_features, diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl index 41391b096cca8..de7f94d581914 100644 --- a/compiler/rustc_codegen_llvm/messages.ftl +++ b/compiler/rustc_codegen_llvm/messages.ftl @@ -10,11 +10,6 @@ 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 {$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 - codegen_llvm_from_llvm_diag = {$message} codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name} ({$kind}): {$message} @@ -70,22 +65,8 @@ 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_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_llvm_write_bytecode = failed to write bytecode to {$path}: {$err} codegen_llvm_write_ir = failed to write LLVM IR to {$path} diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs index ecf108f988f0d..5141f486e082b 100644 --- a/compiler/rustc_codegen_llvm/src/errors.rs +++ b/compiler/rustc_codegen_llvm/src/errors.rs @@ -3,52 +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(Diagnostic)] -#[diag(codegen_llvm_unstable_ctarget_feature)] -#[note] -pub(crate) struct UnstableCTargetFeature<'a> { - pub feature: &'a str, -} - -#[derive(Diagnostic)] -#[diag(codegen_llvm_forbidden_ctarget_feature)] -#[note] -#[note(codegen_llvm_forbidden_ctarget_feature_issue)] -pub(crate) struct ForbiddenCTargetFeature<'a> { - pub feature: &'a str, - pub enabled: &'a str, - pub reason: &'a str, -} - -#[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> { diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 8f57f0983abb9..d3a3498ff10bd 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -8,25 +8,17 @@ use std::{ptr, slice, str}; use libc::c_int; use rustc_codegen_ssa::TargetConfig; use rustc_codegen_ssa::base::wants_wasm_eh; -use rustc_codegen_ssa::codegen_attrs::check_tied_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_middle::{bug, target_features}; use rustc_session::Session; use rustc_session::config::{PrintKind, PrintRequest}; -use rustc_span::Symbol; use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport}; -use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES}; use smallvec::{SmallVec, smallvec}; use crate::back::write::create_informational_target_machine; -use crate::errors::{ - FixedX18InvalidArch, ForbiddenCTargetFeature, PossibleFeature, UnknownCTargetFeature, - UnknownCTargetFeaturePrefix, UnstableCTargetFeature, -}; -use crate::llvm; +use crate::{errors, llvm}; static INIT: Once = Once::new(); @@ -195,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 { - 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> { @@ -216,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 . +/// The commit in use can be found via the `llvm-project` submodule in +/// 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> { let arch = if sess.target.arch == "x86_64" { "x86" @@ -335,98 +322,25 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { // the target CPU, that is still expanded to target features (with all their implied features) // by LLVM. let target_machine = create_informational_target_machine(sess, true); - // Compute which of the known target features are enabled in the 'base' target machine. We only - // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now. - let mut features: FxHashSet = sess - .target - .rust_target_features() - .iter() - .filter(|(feature, _, _)| { - // skip checking special features, as LLVM may not understand them - if RUSTC_SPECIAL_FEATURES.contains(feature) { - return true; - } - if let Some(feat) = to_llvm_features(sess, feature) { - for llvm_feature in feat { - let cstr = SmallCStr::new(llvm_feature); - // `LLVMRustHasFeature` is moderately expensive. On targets with many - // features (e.g. x86) these calls take a non-trivial fraction of runtime - // when compiling very small programs. - if !unsafe { llvm::LLVMRustHasFeature(target_machine.raw(), cstr.as_ptr()) } { - return false; - } + + let (unstable_target_features, target_features) = target_features::cfg(sess, |feature| { + if let Some(feat) = to_llvm_features(sess, feature) { + // All the LLVM features this expands to must be enabled. + for llvm_feature in feat { + let cstr = SmallCStr::new(llvm_feature); + // `LLVMRustHasFeature` is moderately expensive. On targets with many + // features (e.g. x86) these calls take a non-trivial fraction of runtime + // when compiling very small programs. + if !unsafe { llvm::LLVMRustHasFeature(target_machine.raw(), cstr.as_ptr()) } { + return false; } - true - } else { - false } - }) - .map(|(feature, _, _)| Symbol::intern(feature)) - .collect(); - - // Add enabled and remove disabled features. - for (enabled, feature) in - sess.opts.cg.target_feature.split(',').filter_map(|s| match s.chars().next() { - Some('+') => Some((true, Symbol::intern(&s[1..]))), - Some('-') => Some((false, Symbol::intern(&s[1..]))), - _ => None, - }) - { - if enabled { - // Also add all transitively implied features. - - // 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(feature.as_str()) - .iter() - .map(|s| Symbol::intern(s)), - ); + true } else { - // Remove transitively reverse-implied features. - - // 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.retain(|f| { - if sess.target.implied_target_features(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 - } else { - // We can keep `f`. - true - } - }); + false } - } + }); - // Filter enabled features based on feature gates. - let f = |allow_unstable| { - sess.target - .rust_target_features() - .iter() - .filter_map(|(feature, gate, _)| { - // The `allow_unstable` set is used by rustc internally to determined which target - // features are truly available, so we want to return even perma-unstable - // "forbidden" features. - if allow_unstable - || (gate.in_cfg() - && (sess.is_nightly_build() || gate.requires_nightly().is_none())) - { - Some(Symbol::intern(feature)) - } else { - None - } - }) - .filter(|feature| features.contains(&feature)) - .collect() - }; - - let target_features = f(false); - let unstable_target_features = f(true); let mut cfg = TargetConfig { target_features, unstable_target_features, @@ -763,6 +677,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), ); @@ -773,97 +689,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(',') { - 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 . - 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.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 }); - } - } - } - - // 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 @@ -878,23 +720,14 @@ 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, - }); - } + }, + ); } // -Zfixed-x18 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()); } diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 2621935eecf94..9bbcec70f7097 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -68,9 +68,6 @@ codegen_ssa_failed_to_write = failed to write {$path}: {$error} codegen_ssa_field_associated_value_expected = associated value expected for `{$name}` -codegen_ssa_forbidden_target_feature_attr = - target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason} - codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced @@ -202,8 +199,6 @@ codegen_ssa_metadata_object_file_write = error writing metadata object file: {$e codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload -codegen_ssa_missing_features = add the missing features in a `target_feature` attribute - codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering codegen_ssa_missing_query_depgraph = @@ -273,13 +268,6 @@ codegen_ssa_stripping_debug_info_failed = stripping debug info with `{$util}` fa codegen_ssa_symbol_file_write_failure = failed to write symbols file: {$error} -codegen_ssa_target_feature_disable_or_enable = - the target features {$features} must all be either enabled or disabled together - -codegen_ssa_target_feature_safe_trait = `#[target_feature(..)]` cannot be applied to safe trait method - .label = cannot be applied to safe trait method - .label_def = not an `unsafe` function - codegen_ssa_thorin_decompress_data = failed to decompress compressed section codegen_ssa_thorin_duplicate_unit = duplicate split compilation unit ({$unit}) diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 5d09e62f2742d..85fe95bb432cb 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -5,7 +5,6 @@ use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode}; use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr}; use rustc_attr_parsing::ReprAttr::ReprAlign; use rustc_attr_parsing::{AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr}; -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; @@ -16,15 +15,15 @@ use rustc_middle::middle::codegen_fn_attrs::{ use rustc_middle::mir::mono::Linkage; use rustc_middle::query::Providers; use rustc_middle::span_bug; +use rustc_middle::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr}; 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 tracing::debug; use crate::errors; -use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr}; fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage { use rustc_middle::mir::mono::Linkage::*; @@ -660,7 +659,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { codegen_fn_attrs.flags |= CodegenFnAttrFlags::NEVER_UNWIND; } - if let Some(features) = check_tied_features( + if let Some(features) = rustc_middle::target_features::check_tied_features( tcx.sess, &codegen_fn_attrs .target_features @@ -673,10 +672,10 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { .next() .map_or_else(|| tcx.def_span(did), |a| a.span()); tcx.dcx() - .create_err(errors::TargetFeatureDisableOrEnable { + .create_err(rustc_middle::error::TargetFeatureDisableOrEnable { features, span: Some(span), - missing_features: Some(errors::MissingFeatures), + missing_features: Some(rustc_middle::error::MissingFeatures), }) .emit(); } @@ -684,25 +683,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 { diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index c2064397855f0..7e2c713f92f74 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -10,7 +10,7 @@ use rustc_errors::codes::*; use rustc_errors::{ Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, IntoDiagArg, Level, }; -use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; +use rustc_macros::{Diagnostic, LintDiagnostic}; use rustc_middle::ty::layout::LayoutError; use rustc_middle::ty::{FloatTy, Ty}; use rustc_span::{Span, Symbol}; @@ -1182,25 +1182,6 @@ pub(crate) struct InvalidLinkOrdinalFormat { pub span: Span, } -#[derive(Diagnostic)] -#[diag(codegen_ssa_target_feature_safe_trait)] -pub(crate) struct TargetFeatureSafeTrait { - #[primary_span] - #[label] - pub span: Span, - #[label(codegen_ssa_label_def)] - pub def: Span, -} - -#[derive(Diagnostic)] -#[diag(codegen_ssa_forbidden_target_feature_attr)] -pub struct ForbiddenTargetFeatureAttr<'a> { - #[primary_span] - pub span: Span, - pub feature: &'a str, - pub reason: &'a str, -} - #[derive(Diagnostic)] #[diag(codegen_ssa_failed_to_get_layout)] pub struct FailedToGetLayout<'tcx> { @@ -1254,30 +1235,6 @@ pub(crate) struct ErrorCreatingImportLibrary<'a> { pub error: String, } -pub struct TargetFeatureDisableOrEnable<'a> { - pub features: &'a [&'a str], - pub span: Option, - pub missing_features: Option, -} - -#[derive(Subdiagnostic)] -#[help(codegen_ssa_missing_features)] -pub struct MissingFeatures; - -impl 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; diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 4f9757f198b84..28662e6307402 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -59,7 +59,6 @@ pub mod meth; pub mod mir; pub mod mono_item; pub mod size_of_val; -pub mod target_features; pub mod traits; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } @@ -274,7 +273,6 @@ pub enum CodegenErrors { pub fn provide(providers: &mut Providers) { crate::back::symbol_export::provide(providers); crate::base::provide(providers); - crate::target_features::provide(providers); crate::codegen_attrs::provide(providers); providers.queries.global_backend_features = |_tcx: TyCtxt<'_>, ()| vec![]; } diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs deleted file mode 100644 index 8058cd1b1783a..0000000000000 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ /dev/null @@ -1,202 +0,0 @@ -use rustc_attr_parsing::InstructionSetAttr; -use rustc_data_structures::fx::FxIndexSet; -use rustc_data_structures::unord::{UnordMap, UnordSet}; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::def::DefKind; -use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; -use rustc_middle::middle::codegen_fn_attrs::TargetFeature; -use rustc_middle::query::Providers; -use rustc_middle::ty::TyCtxt; -use rustc_session::parse::feature_err; -use rustc_span::{Span, Symbol, sym}; -use rustc_target::target_features::{self, Stability}; - -use crate::errors; - -/// Compute the enabled target features from the `#[target_feature]` function attribute. -/// Enabled target features are added to `target_features`. -pub(crate) fn from_target_feature_attr( - tcx: TyCtxt<'_>, - attr: &hir::Attribute, - rust_target_features: &UnordMap, - target_features: &mut Vec, -) { - let Some(list) = attr.meta_item_list() else { return }; - let bad_item = |span| { - let msg = "malformed `target_feature` attribute input"; - let code = "enable = \"..\""; - tcx.dcx() - .struct_span_err(span, msg) - .with_span_suggestion(span, "must be of the form", code, Applicability::HasPlaceholders) - .emit(); - }; - let rust_features = tcx.features(); - let abi_feature_constraints = tcx.sess.target.abi_required_features(); - for item in list { - // Only `enable = ...` is accepted in the meta-item list. - if !item.has_name(sym::enable) { - bad_item(item.span()); - continue; - } - - // Must be of the form `enable = "..."` (a string). - let Some(value) = item.value_str() else { - bad_item(item.span()); - continue; - }; - - // We allow comma separation to enable multiple features. - for feature in value.as_str().split(',') { - let Some(stability) = rust_target_features.get(feature) else { - let msg = format!("the feature named `{feature}` is not valid for this target"); - let mut err = tcx.dcx().struct_span_err(item.span(), msg); - err.span_label(item.span(), format!("`{feature}` is not valid for this target")); - if let Some(stripped) = feature.strip_prefix('+') { - let valid = rust_target_features.contains_key(stripped); - if valid { - err.help("consider removing the leading `+` in the feature name"); - } - } - err.emit(); - continue; - }; - - // Only allow target features whose feature gates have been enabled - // and which are permitted to be toggled. - if let Err(reason) = stability.toggle_allowed() { - tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr { - span: item.span(), - feature, - reason, - }); - } else if let Some(nightly_feature) = stability.requires_nightly() - && !rust_features.enabled(nightly_feature) - { - feature_err( - &tcx.sess, - nightly_feature, - item.span(), - format!("the target feature `{feature}` is currently unstable"), - ) - .emit(); - } else { - // Add this and the implied features. - 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 - // 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 - // generating code so "it's fine". - if !tcx.sess.opts.actually_rustdoc { - if abi_feature_constraints.incompatible.contains(&name.as_str()) { - tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr { - span: item.span(), - feature: name.as_str(), - reason: "this feature is incompatible with the target ABI", - }); - } - } - target_features.push(TargetFeature { name, implied: name != feature_sym }) - } - } - } - } -} - -/// Computes the set of target features used in a function for the purposes of -/// inline assembly. -fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet { - let mut target_features = tcx.sess.unstable_target_features.clone(); - if tcx.def_kind(did).has_codegen_attrs() { - let attrs = tcx.codegen_fn_attrs(did); - target_features.extend(attrs.target_features.iter().map(|feature| feature.name)); - match attrs.instruction_set { - None => {} - Some(InstructionSetAttr::ArmA32) => { - // FIXME(#120456) - is `swap_remove` correct? - target_features.swap_remove(&sym::thumb_mode); - } - Some(InstructionSetAttr::ArmT32) => { - target_features.insert(sym::thumb_mode); - } - } - } - - tcx.arena.alloc(target_features) -} - -/// Checks the function annotated with `#[target_feature]` is not a safe -/// trait method implementation, reporting an error if it is. -pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) { - if let DefKind::AssocFn = tcx.def_kind(id) { - let parent_id = tcx.local_parent(id); - if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) { - tcx.dcx().emit_err(errors::TargetFeatureSafeTrait { - span: attr_span, - def: tcx.def_span(id), - }); - } - } -} - -pub(crate) fn provide(providers: &mut Providers) { - *providers = Providers { - rust_target_features: |tcx, cnum| { - assert_eq!(cnum, LOCAL_CRATE); - if tcx.sess.opts.actually_rustdoc { - // HACK: rustdoc would like to pretend that we have all the target features, so we - // have to merge all the lists into one. To ensure an unstable target never prevents - // a stable one from working, we merge the stability info of all instances of the - // same target feature name, with the "most stable" taking precedence. And then we - // hope that this doesn't cause issues anywhere else in the compiler... - let mut result: UnordMap = Default::default(); - for (name, stability) in rustc_target::target_features::all_rust_features() { - use std::collections::hash_map::Entry; - match result.entry(name.to_owned()) { - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(stability); - } - Entry::Occupied(mut occupied_entry) => { - // Merge the two stabilities, "more stable" taking precedence. - match (occupied_entry.get(), stability) { - (Stability::Stable, _) - | ( - Stability::Unstable { .. }, - Stability::Unstable { .. } | Stability::Forbidden { .. }, - ) - | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { - // The stability in the entry is at least as good as the new one, just keep it. - } - _ => { - // Overwrite stabilite. - occupied_entry.insert(stability); - } - } - } - } - } - result - } else { - tcx.sess - .target - .rust_target_features() - .iter() - .map(|(a, b, _)| (a.to_string(), *b)) - .collect() - } - }, - implied_target_features: |tcx, feature: Symbol| { - let feature = feature.as_str(); - UnordSet::from(tcx.sess.target.implied_target_features(feature)) - .into_sorted_stable_ord() - .into_iter() - .map(|s| Symbol::intern(s)) - .collect() - }, - asm_target_features, - ..*providers - } -} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index f4d11a7c0be2d..970f49d81b94b 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -779,7 +779,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock = LazyLock::new(|| { limits::provide(providers); proc_macro_decls::provide(providers); rustc_const_eval::provide(providers); - rustc_middle::hir::provide(providers); + rustc_middle::provide(providers); rustc_borrowck::provide(providers); rustc_incremental::provide(providers); rustc_mir_build::provide(providers); diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 3d27e587b6cb4..48ff9f7bd1cd8 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -78,6 +78,14 @@ middle_erroneous_constant = erroneous constant encountered middle_failed_writing_file = failed to write file {$path}: {$error}" +middle_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! +middle_forbidden_ctarget_feature_issue = for more information, see issue #116344 + +middle_forbidden_target_feature_attr = + target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason} + middle_layout_cycle = a cycle occurred during layout computation @@ -95,6 +103,8 @@ middle_layout_too_generic = the type `{$ty}` does not have a fixed layout middle_layout_unknown = the type `{$ty}` has an unknown layout +middle_missing_features = add the missing features in a `target_feature` attribute + middle_opaque_hidden_type_mismatch = concrete type differs from previous defining opaque type use .label = expected `{$self_ty}`, got `{$other_ty}` @@ -112,8 +122,29 @@ middle_strict_coherence_needs_negative_coherence = to use `strict_coherence` on this trait, the `with_negative_coherence` feature must be enabled .label = due to this attribute +middle_target_feature_disable_or_enable = + the target features {$features} must all be either enabled or disabled together + +middle_target_feature_safe_trait = `#[target_feature(..)]` cannot be applied to safe trait method + .label = cannot be applied to safe trait method + .label_def = not an `unsafe` function + middle_type_length_limit = reached the type-length limit while instantiating `{$shrunk}` +middle_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 + +middle_unknown_ctarget_feature_prefix = + unknown feature specified for `-Ctarget-feature`: `{$feature}` + .note = features must begin with a `+` to enable or `-` to disable it + +middle_unstable_ctarget_feature = + unstable feature specified for `-Ctarget-feature`: `{$feature}` + .note = this feature is not stably supported; its behavior can change in the future + middle_unsupported_union = we don't support unions yet: '{$ty_name}' middle_written_to_path = the full type name has been written to '{$path}' diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index bd315577efb5e..2054feecc9d4e 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -2,10 +2,14 @@ use std::path::{Path, PathBuf}; use std::{fmt, io}; use rustc_errors::codes::*; -use rustc_errors::{DiagArgName, DiagArgValue, DiagMessage}; +use rustc_errors::{ + Diag, DiagArgName, DiagArgValue, DiagCtxtHandle, DiagMessage, Diagnostic, EmissionGuarantee, + Level, +}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; +use crate::fluent_generated as fluent; use crate::ty::Ty; #[derive(Diagnostic)] @@ -170,3 +174,87 @@ pub(crate) struct TypeLengthLimit { pub path: PathBuf, pub type_length: usize, } + +#[derive(Diagnostic)] +#[diag(middle_forbidden_target_feature_attr)] +pub struct ForbiddenTargetFeatureAttr<'a> { + #[primary_span] + pub span: Span, + pub feature: &'a str, + pub reason: &'a str, +} + +#[derive(Diagnostic)] +#[diag(middle_target_feature_safe_trait)] +pub(crate) struct TargetFeatureSafeTrait { + #[primary_span] + #[label] + pub span: Span, + #[label(middle_label_def)] + pub def: Span, +} + +#[derive(Diagnostic)] +#[diag(middle_unknown_ctarget_feature_prefix)] +#[note] +pub(crate) struct UnknownCTargetFeaturePrefix<'a> { + pub feature: &'a str, +} + +#[derive(Subdiagnostic)] +pub(crate) enum PossibleFeature<'a> { + #[help(middle_possible_feature)] + Some { rust_feature: &'a str }, + #[help(middle_consider_filing_feature_request)] + None, +} + +#[derive(Diagnostic)] +#[diag(middle_unknown_ctarget_feature)] +#[note] +pub(crate) struct UnknownCTargetFeature<'a> { + pub feature: &'a str, + #[subdiagnostic] + pub rust_feature: PossibleFeature<'a>, +} + +#[derive(Diagnostic)] +#[diag(middle_unstable_ctarget_feature)] +#[note] +pub(crate) struct UnstableCTargetFeature<'a> { + pub feature: &'a str, +} + +#[derive(Diagnostic)] +#[diag(middle_forbidden_ctarget_feature)] +#[note] +#[note(middle_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, + pub missing_features: Option, +} + +#[derive(Subdiagnostic)] +#[help(middle_missing_features)] +pub struct MissingFeatures; + +impl Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_> { + fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> { + let mut diag = Diag::new(dcx, level, fluent::middle_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 + } +} diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index a28dcb0cb8efd..e31d3610a6d3f 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -177,7 +177,7 @@ impl<'tcx> TyCtxt<'tcx> { } } -pub fn provide(providers: &mut Providers) { +pub(crate) fn provide(providers: &mut Providers) { providers.hir_crate_items = map::hir_crate_items; providers.crate_hash = map::crate_hash; providers.hir_module_items = map::hir_module_items; diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 979608df79c08..26392a4959b62 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -79,6 +79,7 @@ pub mod lint; pub mod metadata; pub mod middle; pub mod mir; +pub mod target_features; pub mod thir; pub mod traits; pub mod ty; @@ -94,3 +95,8 @@ pub mod dep_graph; extern crate self as rustc_middle; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } + +pub fn provide(providers: &mut util::Providers) { + hir::provide(providers); + target_features::provide(providers); +} diff --git a/compiler/rustc_middle/src/target_features.rs b/compiler/rustc_middle/src/target_features.rs new file mode 100644 index 0000000000000..1be312d7de989 --- /dev/null +++ b/compiler/rustc_middle/src/target_features.rs @@ -0,0 +1,451 @@ +//! Shared utilities for dealing with target features that haven't (yet) found a better home. +use rustc_attr_data_structures::InstructionSetAttr; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_data_structures::unord::{UnordMap, UnordSet}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; +use rustc_session::Session; +use rustc_session::parse::feature_err; +use rustc_span::{Span, Symbol, sym}; +use rustc_target::target_features::{ + self, RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability, +}; +use smallvec::SmallVec; + +use crate::error; +use crate::middle::codegen_fn_attrs::TargetFeature; +use crate::query::Providers; +use crate::ty::TyCtxt; + +/// Compute the enabled target features from the `#[target_feature]` function attribute. +/// Enabled target features are added to `target_features`. +pub fn from_target_feature_attr( + tcx: TyCtxt<'_>, + attr: &hir::Attribute, + rust_target_features: &UnordMap, + target_features: &mut Vec, +) { + let Some(list) = attr.meta_item_list() else { return }; + let bad_item = |span| { + let msg = "malformed `target_feature` attribute input"; + let code = "enable = \"..\""; + tcx.dcx() + .struct_span_err(span, msg) + .with_span_suggestion(span, "must be of the form", code, Applicability::HasPlaceholders) + .emit(); + }; + let rust_features = tcx.features(); + let abi_feature_constraints = tcx.sess.target.abi_required_features(); + for item in list { + // Only `enable = ...` is accepted in the meta-item list. + if !item.has_name(sym::enable) { + bad_item(item.span()); + continue; + } + + // Must be of the form `enable = "..."` (a string). + let Some(value) = item.value_str() else { + bad_item(item.span()); + continue; + }; + + // We allow comma separation to enable multiple features. + for feature in value.as_str().split(',') { + let Some(stability) = rust_target_features.get(feature) else { + let msg = format!("the feature named `{feature}` is not valid for this target"); + let mut err = tcx.dcx().struct_span_err(item.span(), msg); + err.span_label(item.span(), format!("`{feature}` is not valid for this target")); + if let Some(stripped) = feature.strip_prefix('+') { + let valid = rust_target_features.contains_key(stripped); + if valid { + err.help("consider removing the leading `+` in the feature name"); + } + } + err.emit(); + continue; + }; + + // Only allow target features whose feature gates have been enabled + // and which are permitted to be toggled. + if let Err(reason) = stability.toggle_allowed() { + tcx.dcx().emit_err(error::ForbiddenTargetFeatureAttr { + span: item.span(), + feature, + reason, + }); + } else if let Some(nightly_feature) = stability.requires_nightly() + && !rust_features.enabled(nightly_feature) + { + feature_err( + &tcx.sess, + nightly_feature, + item.span(), + format!("the target feature `{feature}` is currently unstable"), + ) + .emit(); + } else { + // Add this and the implied features. + 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 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 + // generating code so "it's fine". + if !tcx.sess.opts.actually_rustdoc { + if abi_feature_constraints.incompatible.contains(&name.as_str()) { + tcx.dcx().emit_err(error::ForbiddenTargetFeatureAttr { + span: item.span(), + feature: name.as_str(), + reason: "this feature is incompatible with the target ABI", + }); + } + } + target_features.push(TargetFeature { name, implied: name != feature_sym }) + } + } + } + } +} + +/// Computes the set of target features used in a function for the purposes of +/// inline assembly. +fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet { + let mut target_features = tcx.sess.unstable_target_features.clone(); + if tcx.def_kind(did).has_codegen_attrs() { + let attrs = tcx.codegen_fn_attrs(did); + target_features.extend(attrs.target_features.iter().map(|feature| feature.name)); + match attrs.instruction_set { + None => {} + Some(InstructionSetAttr::ArmA32) => { + // FIXME(#120456) - is `swap_remove` correct? + target_features.swap_remove(&sym::thumb_mode); + } + Some(InstructionSetAttr::ArmT32) => { + target_features.insert(sym::thumb_mode); + } + } + } + + tcx.arena.alloc(target_features) +} + +/// Checks the function annotated with `#[target_feature]` is not a safe +/// trait method implementation, reporting an error if it is. +pub fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) { + if let DefKind::AssocFn = tcx.def_kind(id) { + let parent_id = tcx.local_parent(id); + if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) { + tcx.dcx() + .emit_err(error::TargetFeatureSafeTrait { span: attr_span, def: tcx.def_span(id) }); + } + } +} + +/// Parse the value of `-Ctarget-feature`, also expanding implied features, +/// and call the closure for each (expanded) Rust feature. If the list contains +/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked. +fn parse_rust_feature_flag<'a>( + sess: &'a Session, + err_callback: impl Fn(&'a str), + mut callback: impl FnMut( + /* base_feature */ &'a str, + /* with_implied */ FxHashSet<&'a str>, + /* enable */ bool, + ), +) { + // A cache for the backwards implication map. + let mut inverse_implied_features: Option>> = None; + + for feature in sess.opts.cg.target_feature.split(',') { + if let Some(base_feature) = feature.strip_prefix('+') { + callback(base_feature, sess.target.implied_target_features(base_feature), true) + } else if let Some(base_feature) = feature.strip_prefix('-') { + // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical contraposition. + // So we have to find all the reverse implications of `base_feature` and disable them, too. + + let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| { + let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default(); + for (f, _, is) in sess.target.rust_target_features() { + for i in is.iter() { + set.entry(i).or_default().insert(f); + } + } + set + }); + + // Inverse mplied target features have their own inverse implied target features, so we + // traverse the map until there are no more features to add. + let mut features = FxHashSet::default(); + let mut new_features = vec![base_feature]; + while let Some(new_feature) = new_features.pop() { + if features.insert(new_feature) { + if let Some(implied_features) = inverse_implied_features.get(&new_feature) { + new_features.extend(implied_features) + } + } + } + + callback(base_feature, features, false) + } else if !feature.is_empty() { + err_callback(feature) + } + } +} + +/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically, +/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and +/// 2nd component of the return value, respectively). +/// +/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is enabled +/// in the "base" target machine, i.e., without applying `-Ctarget-feature`. +/// +/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere. +pub fn cfg( + sess: &Session, + mut target_base_has_feature: impl FnMut(&str) -> bool, +) -> (Vec, Vec) { + // Compute which of the known target features are enabled in the 'base' target machine. We only + // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now. + let mut features: UnordSet = sess + .target + .rust_target_features() + .iter() + .filter(|(feature, _, _)| { + // Skip checking special features, those are not known to the backend. + if RUSTC_SPECIAL_FEATURES.contains(feature) { + return true; + } + target_base_has_feature(feature) + }) + .map(|(feature, _, _)| Symbol::intern(feature)) + .collect(); + + // Add enabled and remove disabled features. + parse_rust_feature_flag( + sess, + /* err_callback */ |_| {}, + |_base_feature, new_features, enabled| { + // Iteration order is irrelevant since this only influences an `UnordSet`. + #[allow(rustc::potential_query_instability)] + if enabled { + features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); + } else { + // Remove `new_features` from `features`. + for new in new_features { + features.remove(&Symbol::intern(new)); + } + } + }, + ); + + // Filter enabled features based on feature gates. + let f = |allow_unstable| { + sess.target + .rust_target_features() + .iter() + .filter_map(|(feature, gate, _)| { + // The `allow_unstable` set is used by rustc internally to determine which target + // features are truly available, so we want to return even perma-unstable + // "forbidden" features. + if allow_unstable + || (gate.in_cfg() + && (sess.is_nightly_build() || gate.requires_nightly().is_none())) + { + Some(Symbol::intern(feature)) + } else { + None + } + }) + .filter(|feature| features.contains(&feature)) + .collect() + }; + + (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![]; + parse_rust_feature_flag( + sess, + /* err_callback */ + |feature| { + if diagnostics { + sess.dcx().emit_warn(error::UnknownCTargetFeaturePrefix { feature }); + } + }, + |base_feature, new_features, enable| { + // Skip features that are meant for rustc, not the backend. + if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) { + return; + } + + rust_features.extend( + UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), + ); + // Check feature validity. + if diagnostics { + let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_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(&base_feature) + && !backend_features.contains(&rust_feature) + { + Some(rust_feature) + } else { + None + } + }); + let unknown_feature = if let Some(rust_feature) = rust_feature { + error::UnknownCTargetFeature { + feature: base_feature, + rust_feature: error::PossibleFeature::Some { rust_feature }, + } + } else { + error::UnknownCTargetFeature { + feature: base_feature, + rust_feature: error::PossibleFeature::None, + } + }; + sess.dcx().emit_warn(unknown_feature); + } + Some((_, stability, _)) => { + if let Err(reason) = stability.toggle_allowed() { + sess.dcx().emit_warn(error::ForbiddenCTargetFeature { + feature: base_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(error::UnstableCTargetFeature { feature: base_feature }); + } + } + } + } + }, + ); + + if diagnostics { + // FIXME(nagisa): figure out how to not allocate a full hashmap here. + if let Some(f) = check_tied_features( + sess, + &FxHashMap::from_iter(rust_features.iter().map(|&(enable, feature)| (feature, enable))), + ) { + sess.dcx().emit_err(error::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); + } +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { + rust_target_features: |tcx, cnum| { + assert_eq!(cnum, LOCAL_CRATE); + if tcx.sess.opts.actually_rustdoc { + // HACK: rustdoc would like to pretend that we have all the target features, so we + // have to merge all the lists into one. To ensure an unstable target never prevents + // a stable one from working, we merge the stability info of all instances of the + // same target feature name, with the "most stable" taking precedence. And then we + // hope that this doesn't cause issues anywhere else in the compiler... + let mut result: UnordMap = Default::default(); + for (name, stability) in rustc_target::target_features::all_rust_features() { + use std::collections::hash_map::Entry; + match result.entry(name.to_owned()) { + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(stability); + } + Entry::Occupied(mut occupied_entry) => { + // Merge the two stabilities, "more stable" taking precedence. + match (occupied_entry.get(), stability) { + (Stability::Stable, _) + | ( + Stability::Unstable { .. }, + Stability::Unstable { .. } | Stability::Forbidden { .. }, + ) + | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { + // The stability in the entry is at least as good as the new one, just keep it. + } + _ => { + // Overwrite stabilite. + occupied_entry.insert(stability); + } + } + } + } + } + result + } else { + tcx.sess + .target + .rust_target_features() + .iter() + .map(|(a, b, _)| (a.to_string(), *b)) + .collect() + } + }, + implied_target_features: |tcx, feature: Symbol| { + let feature = feature.as_str(); + UnordSet::from(tcx.sess.target.implied_target_features(feature)) + .into_sorted_stable_ord() + .into_iter() + .map(|s| Symbol::intern(s)) + .collect() + }, + asm_target_features, + ..*providers + } +} diff --git a/tests/codegen/target-feature-negative-implication.rs b/tests/codegen/target-feature-negative-implication.rs new file mode 100644 index 0000000000000..36cd82dd8cf5a --- /dev/null +++ b/tests/codegen/target-feature-negative-implication.rs @@ -0,0 +1,20 @@ +//@ add-core-stubs +//@ needs-llvm-components: x86 +//@ compile-flags: --target=x86_64-unknown-linux-gnu +//@ compile-flags: -Ctarget-feature=-avx2 + +#![feature(no_core, lang_items)] +#![crate_type = "lib"] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[no_mangle] +pub unsafe fn banana() { + // CHECK-LABEL: @banana() + // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] { +} + +// CHECK: attributes [[BANANAATTRS]] +// CHECK-SAME: -avx512 diff --git a/tests/codegen/target-feature-overrides.rs b/tests/codegen/target-feature-overrides.rs index 0fc1e0136b3f3..eb19b0de2fa8a 100644 --- a/tests/codegen/target-feature-overrides.rs +++ b/tests/codegen/target-feature-overrides.rs @@ -1,3 +1,4 @@ +// ignore-tidy-linelength //@ add-core-stubs //@ revisions: COMPAT INCOMPAT //@ needs-llvm-components: x86 @@ -39,7 +40,7 @@ pub unsafe fn banana() -> u32 { // CHECK: attributes [[APPLEATTRS]] // COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}" -// INCOMPAT-SAME: "target-features"="-avx2,-avx,+avx,{{.*}}" +// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}},+avx{{(,\+[^,]+)*}}" // CHECK: attributes [[BANANAATTRS]] // COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}" -// INCOMPAT-SAME: "target-features"="-avx2,-avx" +// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}}" diff --git a/tests/codegen/tied-features-strength.rs b/tests/codegen/tied-features-strength.rs index 6be0e21e0ef3a..81499c070d19a 100644 --- a/tests/codegen/tied-features-strength.rs +++ b/tests/codegen/tied-features-strength.rs @@ -4,14 +4,23 @@ //@ compile-flags: --crate-type=rlib --target=aarch64-unknown-linux-gnu //@ needs-llvm-components: aarch64 +// Rust made SVE require neon. //@ [ENABLE_SVE] compile-flags: -C target-feature=+sve -Copt-level=0 -// ENABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+sve,?)|(\+neon,?)|(\+fp-armv8,?))*}}" } +// ENABLE_SVE: attributes #0 +// ENABLE_SVE-SAME: +neon +// ENABLE_SVE-SAME: +sve +// However, disabling SVE does not disable neon. //@ [DISABLE_SVE] compile-flags: -C target-feature=-sve -Copt-level=0 -// DISABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-sve,?)|(\+neon,?))*}}" } +// DISABLE_SVE: attributes #0 +// DISABLE_SVE-NOT: -neon +// DISABLE_SVE-SAME: -sve +// OTOH, neon fn `fp-armv8` are fully tied; toggling neon must toggle `fp-armv8` the same way. //@ [DISABLE_NEON] compile-flags: -C target-feature=-neon -Copt-level=0 -// DISABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-fp-armv8,?)|(-neon,?))*}}" } +// DISABLE_NEON: attributes #0 +// DISABLE_NEON-SAME: -neon +// DISABLE_NEON-SAME: -fp-armv8 //@ [ENABLE_NEON] compile-flags: -C target-feature=+neon -Copt-level=0 // ENABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fp-armv8,?)|(\+neon,?))*}}" } diff --git a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr index 2dca0c220332b..0b2d71f97d0d4 100644 --- a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr +++ b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr @@ -7,13 +7,5 @@ warning: unstable feature specified for `-Ctarget-feature`: `d` | = note: this feature is not stably supported; its behavior can change in the future -warning: unstable feature specified for `-Ctarget-feature`: `f` - | - = note: this feature is not stably supported; its behavior can change in the future - -warning: unstable feature specified for `-Ctarget-feature`: `zicsr` - | - = note: this feature is not stably supported; its behavior can change in the future - -warning: 4 warnings emitted +warning: 2 warnings emitted diff --git a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs index 68e1d3b9ddc61..a68db600c8596 100644 --- a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs +++ b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs @@ -18,5 +18,3 @@ pub trait Freeze {} //~? WARN must be disabled to ensure that the ABI of the current target can be implemented correctly //~? WARN unstable feature specified for `-Ctarget-feature` -//[riscv]~? WARN unstable feature specified for `-Ctarget-feature` -//[riscv]~? WARN unstable feature specified for `-Ctarget-feature`