about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-11-16 10:00:16 +0100
committerRalf Jung <post@ralfj.de>2024-12-11 22:11:15 +0100
commit2d887a5c5ca65287bb40dc5b2d108e2bb2dd6719 (patch)
treeaf562288d549ff410de0ed4bd055d3fb6ba17279
parent1f3bf231e160b9869e2a85260fd6805304bfcee2 (diff)
downloadrust-2d887a5c5ca65287bb40dc5b2d108e2bb2dd6719.tar.gz
rust-2d887a5c5ca65287bb40dc5b2d108e2bb2dd6719.zip
generalize 'forbidden feature' concept so that even (un)stable feature can be invalid to toggle
Also rename some things for extra clarity
-rw-r--r--compiler/rustc_codegen_cranelift/src/lib.rs6
-rw-r--r--compiler/rustc_codegen_gcc/messages.ftl2
-rw-r--r--compiler/rustc_codegen_gcc/src/errors.rs1
-rw-r--r--compiler/rustc_codegen_gcc/src/gcc_util.rs18
-rw-r--r--compiler/rustc_codegen_gcc/src/lib.rs11
-rw-r--r--compiler/rustc_codegen_llvm/src/lib.rs6
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm_util.rs28
-rw-r--r--compiler/rustc_codegen_ssa/src/target_features.rs53
-rw-r--r--compiler/rustc_codegen_ssa/src/traits/backend.rs4
-rw-r--r--compiler/rustc_interface/src/util.rs4
-rw-r--r--compiler/rustc_middle/src/query/mod.rs2
-rw-r--r--compiler/rustc_session/src/config/cfg.rs2
-rw-r--r--compiler/rustc_target/src/target_features.rs732
13 files changed, 468 insertions, 401 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs
index 9f552b3feb9..75f5b32daaa 100644
--- a/compiler/rustc_codegen_cranelift/src/lib.rs
+++ b/compiler/rustc_codegen_cranelift/src/lib.rs
@@ -175,7 +175,11 @@ impl CodegenBackend for CraneliftCodegenBackend {
         }
     }
 
-    fn target_features(&self, sess: &Session, _allow_unstable: bool) -> Vec<rustc_span::Symbol> {
+    fn target_features_cfg(
+        &self,
+        sess: &Session,
+        _allow_unstable: bool,
+    ) -> Vec<rustc_span::Symbol> {
         // FIXME return the actually used target features. this is necessary for #[cfg(target_feature)]
         if sess.target.arch == "x86_64" && sess.target.os != "none" {
             // x86_64 mandates SSE2 support
diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl
index 26ddc5732dd..85fa17a6ba5 100644
--- a/compiler/rustc_codegen_gcc/messages.ftl
+++ b/compiler/rustc_codegen_gcc/messages.ftl
@@ -9,7 +9,7 @@ codegen_gcc_lto_not_supported =
     LTO is not supported. You may get a linker error.
 
 codegen_gcc_forbidden_ctarget_feature =
-    target feature `{$feature}` cannot be toggled with `-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
diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs
index 7a586b5b04c..56849cc8610 100644
--- a/compiler/rustc_codegen_gcc/src/errors.rs
+++ b/compiler/rustc_codegen_gcc/src/errors.rs
@@ -28,6 +28,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
 #[diag(codegen_gcc_forbidden_ctarget_feature)]
 pub(crate) struct ForbiddenCTargetFeature<'a> {
     pub feature: &'a str,
+    pub reason: &'a str,
 }
 
 #[derive(Subdiagnostic)]
diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 65279c9495a..3717e12020f 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -5,7 +5,7 @@ use rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_middle::bug;
 use rustc_session::Session;
-use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
+use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
 use smallvec::{SmallVec, smallvec};
 
 use crate::errors::{
@@ -94,13 +94,15 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
                         };
                         sess.dcx().emit_warn(unknown_feature);
                     }
-                    Some((_, Stability::Stable, _)) => {}
-                    Some((_, Stability::Unstable(_), _)) => {
-                        // An unstable feature. Warn about using it.
-                        sess.dcx().emit_warn(UnstableCTargetFeature { feature });
-                    }
-                    Some((_, Stability::Forbidden { .. }, _)) => {
-                        sess.dcx().emit_err(ForbiddenCTargetFeature { feature });
+                    Some((_, stability, _)) => {
+                        if let Err(reason) = stability.compute(&sess.target).allow_toggle() {
+                            sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
+                        } else if stability.requires_nightly().is_some() {
+                            // An unstable feature. Warn about using it. (It makes little sense
+                            // to hard-error here since we just warn about fully unknown
+                            // features above).
+                            sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                        }
                     }
                 }
 
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index 452e92bffa2..764e84be1fe 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -260,8 +260,8 @@ impl CodegenBackend for GccCodegenBackend {
             .join(sess)
     }
 
-    fn target_features(&self, sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
-        target_features(sess, allow_unstable, &self.target_info)
+    fn target_features_cfg(&self, sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
+        target_features_cfg(sess, allow_unstable, &self.target_info)
     }
 }
 
@@ -472,7 +472,8 @@ fn to_gcc_opt_level(optlevel: Option<OptLevel>) -> OptimizationLevel {
     }
 }
 
-pub fn target_features(
+/// Returns the features that should be set in `cfg(target_feature)`.
+fn target_features_cfg(
     sess: &Session,
     allow_unstable: bool,
     target_info: &LockedTargetInfo,
@@ -481,9 +482,9 @@ pub fn target_features(
     sess.target
         .rust_target_features()
         .iter()
-        .filter(|(_, gate, _)| gate.is_supported())
+        .filter(|(_, gate, _)| gate.in_cfg())
         .filter_map(|&(feature, gate, _)| {
-            if sess.is_nightly_build() || allow_unstable || gate.is_stable() {
+            if sess.is_nightly_build() || allow_unstable || gate.requires_nightly().is_none() {
                 Some(feature)
             } else {
                 None
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index 5235891a18d..af8562db054 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -27,7 +27,7 @@ use std::mem::ManuallyDrop;
 use back::owned_target_machine::OwnedTargetMachine;
 use back::write::{create_informational_target_machine, create_target_machine};
 use errors::ParseTargetMachineConfig;
-pub use llvm_util::target_features;
+pub use llvm_util::target_features_cfg;
 use rustc_ast::expand::allocator::AllocatorKind;
 use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule};
 use rustc_codegen_ssa::back::write::{
@@ -330,8 +330,8 @@ impl CodegenBackend for LlvmCodegenBackend {
         llvm_util::print_version();
     }
 
-    fn target_features(&self, sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
-        target_features(sess, allow_unstable)
+    fn target_features_cfg(&self, sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
+        target_features_cfg(sess, allow_unstable)
     }
 
     fn codegen_crate<'tcx>(
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 07eb89e6041..3f53856f98d 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -17,7 +17,7 @@ use rustc_session::Session;
 use rustc_session::config::{PrintKind, PrintRequest};
 use rustc_span::symbol::Symbol;
 use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport};
-use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability};
+use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES};
 
 use crate::back::write::create_informational_target_machine;
 use crate::errors::{
@@ -300,7 +300,7 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
 /// Must express features in the way Rust understands them.
 ///
 /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled outside codegen.
-pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
+pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
     let mut features: FxHashSet<Symbol> = Default::default();
 
     // Add base features for the target.
@@ -316,7 +316,7 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
         sess.target
             .rust_target_features()
             .iter()
-            .filter(|(_, gate, _)| gate.is_supported())
+            .filter(|(_, gate, _)| gate.in_cfg())
             .filter(|(feature, _, _)| {
                 // skip checking special features, as LLVM may not understand them
                 if RUSTC_SPECIAL_FEATURES.contains(feature) {
@@ -372,9 +372,9 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
     sess.target
         .rust_target_features()
         .iter()
-        .filter(|(_, gate, _)| gate.is_supported())
+        .filter(|(_, gate, _)| gate.in_cfg())
         .filter_map(|&(feature, gate, _)| {
-            if sess.is_nightly_build() || allow_unstable || gate.is_stable() {
+            if sess.is_nightly_build() || allow_unstable || gate.requires_nightly().is_none() {
                 Some(feature)
             } else {
                 None
@@ -493,7 +493,7 @@ fn print_target_features(sess: &Session, tm: &llvm::TargetMachine, out: &mut Str
         .rust_target_features()
         .iter()
         .filter_map(|(feature, gate, _implied)| {
-            if !gate.is_supported() {
+            if !gate.in_cfg() {
                 // Only list (experimentally) supported features.
                 return None;
             }
@@ -716,13 +716,15 @@ pub(crate) fn global_llvm_features(
                             };
                             sess.dcx().emit_warn(unknown_feature);
                         }
-                        Some((_, Stability::Stable, _)) => {}
-                        Some((_, Stability::Unstable(_), _)) => {
-                            // An unstable feature. Warn about using it.
-                            sess.dcx().emit_warn(UnstableCTargetFeature { feature });
-                        }
-                        Some((_, Stability::Forbidden { reason }, _)) => {
-                            sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
+                        Some((_, stability, _)) => {
+                            if let Err(reason) = stability.compute(&sess.target).allow_toggle() {
+                                sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
+                            } else if stability.requires_nightly().is_some() {
+                                // An unstable feature. Warn about using it. (It makes little sense
+                                // to hard-error here since we just warn about fully unknown
+                                // features above).
+                                sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                            }
                         }
                     }
 
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index eee7cc75400..b3057325bd6 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -11,7 +11,7 @@ use rustc_middle::ty::TyCtxt;
 use rustc_session::parse::feature_err;
 use rustc_span::Span;
 use rustc_span::symbol::{Symbol, sym};
-use rustc_target::target_features::{self, Stability};
+use rustc_target::target_features;
 
 use crate::errors;
 
@@ -20,7 +20,7 @@ use crate::errors;
 pub(crate) fn from_target_feature_attr(
     tcx: TyCtxt<'_>,
     attr: &ast::Attribute,
-    rust_target_features: &UnordMap<String, target_features::Stability>,
+    rust_target_features: &UnordMap<String, target_features::StabilityComputed>,
     target_features: &mut Vec<TargetFeature>,
 ) {
     let Some(list) = attr.meta_item_list() else { return };
@@ -63,32 +63,24 @@ pub(crate) fn from_target_feature_attr(
                 return None;
             };
 
-            // Only allow target features whose feature gates have been enabled.
-            let allowed = match stability {
-                Stability::Forbidden { .. } => false,
-                Stability::Stable => true,
-                Stability::Unstable(name) => rust_features.enabled(*name),
-            };
-            if !allowed {
-                match stability {
-                    Stability::Stable => unreachable!(),
-                    &Stability::Unstable(lang_feature_name) => {
-                        feature_err(
-                            &tcx.sess,
-                            lang_feature_name,
-                            item.span(),
-                            format!("the target feature `{feature}` is currently unstable"),
-                        )
-                        .emit();
-                    }
-                    Stability::Forbidden { reason } => {
-                        tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
-                            span: item.span(),
-                            feature,
-                            reason,
-                        });
-                    }
-                }
+            // Only allow target features whose feature gates have been enabled
+            // and which are permitted to be toggled.
+            if let Err(reason) = stability.allow_toggle() {
+                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();
             }
             Some(Symbol::intern(feature))
         }));
@@ -156,18 +148,19 @@ pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         rust_target_features: |tcx, cnum| {
             assert_eq!(cnum, LOCAL_CRATE);
+            let target = &tcx.sess.target;
             if tcx.sess.opts.actually_rustdoc {
                 // rustdoc needs to be able to document functions that use all the features, so
                 // whitelist them all
                 rustc_target::target_features::all_rust_features()
-                    .map(|(a, b)| (a.to_string(), b))
+                    .map(|(a, b)| (a.to_string(), b.compute(target)))
                     .collect()
             } else {
                 tcx.sess
                     .target
                     .rust_target_features()
                     .iter()
-                    .map(|&(a, b, _)| (a.to_string(), b))
+                    .map(|&(a, b, _)| (a.to_string(), b.compute(target)))
                     .collect()
             }
         },
diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs
index 5b4a51fc301..4b17db2c49e 100644
--- a/compiler/rustc_codegen_ssa/src/traits/backend.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs
@@ -45,7 +45,9 @@ pub trait CodegenBackend {
 
     fn print(&self, _req: &PrintRequest, _out: &mut String, _sess: &Session) {}
 
-    fn target_features(&self, _sess: &Session, _allow_unstable: bool) -> Vec<Symbol> {
+    /// Returns the features that should be set in `cfg(target_features)`.
+    /// RUSTC_SPECIFIC_FEATURES should be skipped here, those are handled outside codegen.
+    fn target_features_cfg(&self, _sess: &Session, _allow_unstable: bool) -> Vec<Symbol> {
         vec![]
     }
 
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
index d3213b1263c..2af25bfd3aa 100644
--- a/compiler/rustc_interface/src/util.rs
+++ b/compiler/rustc_interface/src/util.rs
@@ -35,10 +35,10 @@ pub type MakeBackendFn = fn() -> Box<dyn CodegenBackend>;
 pub fn add_configuration(cfg: &mut Cfg, sess: &mut Session, codegen_backend: &dyn CodegenBackend) {
     let tf = sym::target_feature;
 
-    let unstable_target_features = codegen_backend.target_features(sess, true);
+    let unstable_target_features = codegen_backend.target_features_cfg(sess, true);
     sess.unstable_target_features.extend(unstable_target_features.iter().cloned());
 
-    let target_features = codegen_backend.target_features(sess, false);
+    let target_features = codegen_backend.target_features_cfg(sess, false);
     sess.target_features.extend(target_features.iter().cloned());
 
     cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat))));
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index fc3d690a8a9..b1ffae2adee 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2230,7 +2230,7 @@ rustc_queries! {
     }
 
     /// Returns the Rust target features for the current target. These are not always the same as LLVM target features!
-    query rust_target_features(_: CrateNum) -> &'tcx UnordMap<String, rustc_target::target_features::Stability> {
+    query rust_target_features(_: CrateNum) -> &'tcx UnordMap<String, rustc_target::target_features::StabilityComputed> {
         arena_cache
         eval_always
         desc { "looking up Rust target features" }
diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs
index 99d9d5b7665..43cf1324edd 100644
--- a/compiler/rustc_session/src/config/cfg.rs
+++ b/compiler/rustc_session/src/config/cfg.rs
@@ -371,7 +371,7 @@ impl CheckCfg {
 
         ins!(sym::target_feature, empty_values).extend(
             rustc_target::target_features::all_rust_features()
-                .filter(|(_, s)| s.is_supported())
+                .filter(|(_, s)| s.in_cfg())
                 .map(|(f, _s)| f)
                 .chain(rustc_target::target_features::RUSTC_SPECIFIC_FEATURES.iter().cloned())
                 .map(Symbol::intern),
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 3a130607265..717e64fe6c8 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -5,6 +5,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_span::symbol::{Symbol, sym};
 
+use crate::spec::Target;
+
 /// Features that control behaviour of rustc, rather than the codegen.
 /// These exist globally and are not in the target-specific lists below.
 pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];
@@ -15,45 +17,103 @@ pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];
 pub const RUSTC_SPECIAL_FEATURES: &[&str] = &["backchain"];
 
 /// Stability information for target features.
+/// `AllowToggle` is the type storing whether (un)stable features can be toggled:
+/// this is initially a function since it can depend on `Target`, but for stable hashing
+/// it needs to be something hashable to we have to make the type generic.
 #[derive(Debug, Clone, Copy)]
-pub enum Stability {
+pub enum Stability<AllowToggle> {
     /// This target feature is stable, it can be used in `#[target_feature]` and
     /// `#[cfg(target_feature)]`.
-    Stable,
-    /// This target feature is unstable; using it in `#[target_feature]` or `#[cfg(target_feature)]`
-    /// requires enabling the given nightly feature.
-    Unstable(Symbol),
-    /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be set in the basic
-    /// target definition. Used in particular for features that change the floating-point ABI.
+    Stable {
+        /// When enabling/dsiabling the feature via `-Ctarget-feature` or `#[target_feature]`,
+        /// determine if that is allowed.
+        allow_toggle: AllowToggle,
+    },
+    /// This target feature is unstable. It is only present in `#[cfg(target_feature)]` on
+    /// nightly and using it in `#[target_feature]` requires enabling the given nightly feature.
+    Unstable {
+        nightly_feature: Symbol,
+        /// See `Stable::allow_toggle` comment above.
+        allow_toggle: AllowToggle,
+    },
+    /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be
+    /// set in the basic target definition. It is never set in `cfg(target_feature)`. Used in
+    /// particular for features that change the floating-point ABI.
     Forbidden { reason: &'static str },
 }
-use Stability::*;
 
-impl<CTX> HashStable<CTX> for Stability {
+/// `Stability` where `allow_toggle` has not been computed yet.
+/// Returns `Ok` if the toggle is allowed, `Err` with an explanation of not.
+pub type StabilityUncomputed = Stability<fn(&Target) -> Result<(), &'static str>>;
+/// `Stability` where `allow_toggle` has already been computed.
+pub type StabilityComputed = Stability<Result<(), &'static str>>;
+
+impl<CTX, AllowToggle: HashStable<CTX>> HashStable<CTX> for Stability<AllowToggle> {
     #[inline]
     fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) {
         std::mem::discriminant(self).hash_stable(hcx, hasher);
         match self {
-            Stable => {}
-            Unstable(sym) => {
-                sym.hash_stable(hcx, hasher);
+            Stability::Stable { allow_toggle } => {
+                allow_toggle.hash_stable(hcx, hasher);
+            }
+            Stability::Unstable { nightly_feature, allow_toggle } => {
+                nightly_feature.hash_stable(hcx, hasher);
+                allow_toggle.hash_stable(hcx, hasher);
             }
-            Forbidden { .. } => {}
+            Stability::Forbidden { reason } => {
+                reason.hash_stable(hcx, hasher);
+            }
+        }
+    }
+}
+
+impl<AllowToggle> Stability<AllowToggle> {
+    /// Returns whether the feature can be queried in `cfg` ever.
+    /// (It might still be nightly-only even if this returns `true`).
+    pub fn in_cfg(self) -> bool {
+        !matches!(self, Stability::Forbidden { .. })
+    }
+
+    /// Returns the nightly feature that is required to toggle or query this target feature. Ensure
+    /// to also check `allow_toggle()` before allowing to toggle!
+    pub fn requires_nightly(self) -> Option<Symbol> {
+        match self {
+            Stability::Unstable { nightly_feature, .. } => Some(nightly_feature),
+            Stability::Stable { .. } => None,
+            Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
         }
     }
 }
 
-impl Stability {
-    pub fn is_stable(self) -> bool {
-        matches!(self, Stable)
+impl StabilityUncomputed {
+    pub fn compute(self, target: &Target) -> StabilityComputed {
+        use Stability::*;
+        match self {
+            Stable { allow_toggle } => Stable { allow_toggle: allow_toggle(target) },
+            Unstable { nightly_feature, allow_toggle } => {
+                Unstable { nightly_feature, allow_toggle: allow_toggle(target) }
+            }
+            Forbidden { reason } => Forbidden { reason },
+        }
     }
+}
 
-    /// Forbidden features are not supported.
-    pub fn is_supported(self) -> bool {
-        !matches!(self, Forbidden { .. })
+impl StabilityComputed {
+    pub fn allow_toggle(self) -> Result<(), &'static str> {
+        match self {
+            Stability::Stable { allow_toggle } => allow_toggle,
+            Stability::Unstable { allow_toggle, .. } => allow_toggle,
+            Stability::Forbidden { reason } => Err(reason),
+        }
     }
 }
 
+// Constructors for the list below, defaulting to "always allow toggle".
+const STABLE: StabilityUncomputed = Stability::Stable { allow_toggle: |_target| Ok(()) };
+const fn unstable(nightly_feature: Symbol) -> StabilityUncomputed {
+    Stability::Unstable { nightly_feature, allow_toggle: |_target| Ok(()) }
+}
+
 // Here we list target features that rustc "understands": they can be used in `#[target_feature]`
 // and `#[cfg(target_feature)]`. They also do not trigger any warnings when used with
 // `-Ctarget-feature`.
@@ -99,181 +159,181 @@ impl Stability {
 // Both of these are also applied transitively.
 type ImpliedFeatures = &'static [&'static str];
 
-const ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const ARM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("aclass", Unstable(sym::arm_target_feature), &[]),
-    ("aes", Unstable(sym::arm_target_feature), &["neon"]),
-    ("crc", Unstable(sym::arm_target_feature), &[]),
-    ("d32", Unstable(sym::arm_target_feature), &[]),
-    ("dotprod", Unstable(sym::arm_target_feature), &["neon"]),
-    ("dsp", Unstable(sym::arm_target_feature), &[]),
-    ("fp-armv8", Unstable(sym::arm_target_feature), &["vfp4"]),
-    ("i8mm", Unstable(sym::arm_target_feature), &["neon"]),
-    ("mclass", Unstable(sym::arm_target_feature), &[]),
-    ("neon", Unstable(sym::arm_target_feature), &["vfp3"]),
-    ("rclass", Unstable(sym::arm_target_feature), &[]),
-    ("sha2", Unstable(sym::arm_target_feature), &["neon"]),
-    ("soft-float", Forbidden { reason: "unsound because it changes float ABI" }, &[]),
+    ("aclass", unstable(sym::arm_target_feature), &[]),
+    ("aes", unstable(sym::arm_target_feature), &["neon"]),
+    ("crc", unstable(sym::arm_target_feature), &[]),
+    ("d32", unstable(sym::arm_target_feature), &[]),
+    ("dotprod", unstable(sym::arm_target_feature), &["neon"]),
+    ("dsp", unstable(sym::arm_target_feature), &[]),
+    ("fp-armv8", unstable(sym::arm_target_feature), &["vfp4"]),
+    ("i8mm", unstable(sym::arm_target_feature), &["neon"]),
+    ("mclass", unstable(sym::arm_target_feature), &[]),
+    ("neon", unstable(sym::arm_target_feature), &["vfp3"]),
+    ("rclass", unstable(sym::arm_target_feature), &[]),
+    ("sha2", unstable(sym::arm_target_feature), &["neon"]),
+    ("soft-float", Stability::Forbidden { reason: "unsound because it changes float ABI" }, &[]),
     // This is needed for inline assembly, but shouldn't be stabilized as-is
     // since it should be enabled per-function using #[instruction_set], not
     // #[target_feature].
-    ("thumb-mode", Unstable(sym::arm_target_feature), &[]),
-    ("thumb2", Unstable(sym::arm_target_feature), &[]),
-    ("trustzone", Unstable(sym::arm_target_feature), &[]),
-    ("v5te", Unstable(sym::arm_target_feature), &[]),
-    ("v6", Unstable(sym::arm_target_feature), &["v5te"]),
-    ("v6k", Unstable(sym::arm_target_feature), &["v6"]),
-    ("v6t2", Unstable(sym::arm_target_feature), &["v6k", "thumb2"]),
-    ("v7", Unstable(sym::arm_target_feature), &["v6t2"]),
-    ("v8", Unstable(sym::arm_target_feature), &["v7"]),
-    ("vfp2", Unstable(sym::arm_target_feature), &[]),
-    ("vfp3", Unstable(sym::arm_target_feature), &["vfp2", "d32"]),
-    ("vfp4", Unstable(sym::arm_target_feature), &["vfp3"]),
-    ("virtualization", Unstable(sym::arm_target_feature), &[]),
+    ("thumb-mode", unstable(sym::arm_target_feature), &[]),
+    ("thumb2", unstable(sym::arm_target_feature), &[]),
+    ("trustzone", unstable(sym::arm_target_feature), &[]),
+    ("v5te", unstable(sym::arm_target_feature), &[]),
+    ("v6", unstable(sym::arm_target_feature), &["v5te"]),
+    ("v6k", unstable(sym::arm_target_feature), &["v6"]),
+    ("v6t2", unstable(sym::arm_target_feature), &["v6k", "thumb2"]),
+    ("v7", unstable(sym::arm_target_feature), &["v6t2"]),
+    ("v8", unstable(sym::arm_target_feature), &["v7"]),
+    ("vfp2", unstable(sym::arm_target_feature), &[]),
+    ("vfp3", unstable(sym::arm_target_feature), &["vfp2", "d32"]),
+    ("vfp4", unstable(sym::arm_target_feature), &["vfp3"]),
+    ("virtualization", unstable(sym::arm_target_feature), &[]),
     // tidy-alphabetical-end
     // FIXME: need to also forbid turning off `fpregs` on hardfloat targets
 ];
 
-const AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     // FEAT_AES & FEAT_PMULL
-    ("aes", Stable, &["neon"]),
+    ("aes", STABLE, &["neon"]),
     // FEAT_BF16
-    ("bf16", Stable, &[]),
+    ("bf16", STABLE, &[]),
     // FEAT_BTI
-    ("bti", Stable, &[]),
+    ("bti", STABLE, &[]),
     // FEAT_CRC
-    ("crc", Stable, &[]),
+    ("crc", STABLE, &[]),
     // FEAT_CSSC
-    ("cssc", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("cssc", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_DIT
-    ("dit", Stable, &[]),
+    ("dit", STABLE, &[]),
     // FEAT_DotProd
-    ("dotprod", Stable, &["neon"]),
+    ("dotprod", STABLE, &["neon"]),
     // FEAT_DPB
-    ("dpb", Stable, &[]),
+    ("dpb", STABLE, &[]),
     // FEAT_DPB2
-    ("dpb2", Stable, &["dpb"]),
+    ("dpb2", STABLE, &["dpb"]),
     // FEAT_ECV
-    ("ecv", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("ecv", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_F32MM
-    ("f32mm", Stable, &["sve"]),
+    ("f32mm", STABLE, &["sve"]),
     // FEAT_F64MM
-    ("f64mm", Stable, &["sve"]),
+    ("f64mm", STABLE, &["sve"]),
     // FEAT_FAMINMAX
-    ("faminmax", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("faminmax", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_FCMA
-    ("fcma", Stable, &["neon"]),
+    ("fcma", STABLE, &["neon"]),
     // FEAT_FHM
-    ("fhm", Stable, &["fp16"]),
+    ("fhm", STABLE, &["fp16"]),
     // FEAT_FLAGM
-    ("flagm", Stable, &[]),
+    ("flagm", STABLE, &[]),
     // FEAT_FLAGM2
-    ("flagm2", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("flagm2", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_FP16
     // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
-    ("fp16", Stable, &["neon"]),
+    ("fp16", STABLE, &["neon"]),
     // FEAT_FP8
-    ("fp8", Unstable(sym::aarch64_unstable_target_feature), &["faminmax", "lut", "bf16"]),
+    ("fp8", unstable(sym::aarch64_unstable_target_feature), &["faminmax", "lut", "bf16"]),
     // FEAT_FP8DOT2
-    ("fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["fp8dot4"]),
+    ("fp8dot2", unstable(sym::aarch64_unstable_target_feature), &["fp8dot4"]),
     // FEAT_FP8DOT4
-    ("fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["fp8fma"]),
+    ("fp8dot4", unstable(sym::aarch64_unstable_target_feature), &["fp8fma"]),
     // FEAT_FP8FMA
-    ("fp8fma", Unstable(sym::aarch64_unstable_target_feature), &["fp8"]),
+    ("fp8fma", unstable(sym::aarch64_unstable_target_feature), &["fp8"]),
     // FEAT_FRINTTS
-    ("frintts", Stable, &[]),
+    ("frintts", STABLE, &[]),
     // FEAT_HBC
-    ("hbc", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("hbc", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_I8MM
-    ("i8mm", Stable, &[]),
+    ("i8mm", STABLE, &[]),
     // FEAT_JSCVT
     // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
-    ("jsconv", Stable, &["neon"]),
+    ("jsconv", STABLE, &["neon"]),
     // FEAT_LOR
-    ("lor", Stable, &[]),
+    ("lor", STABLE, &[]),
     // FEAT_LSE
-    ("lse", Stable, &[]),
+    ("lse", STABLE, &[]),
     // FEAT_LSE128
-    ("lse128", Unstable(sym::aarch64_unstable_target_feature), &["lse"]),
+    ("lse128", unstable(sym::aarch64_unstable_target_feature), &["lse"]),
     // FEAT_LSE2
-    ("lse2", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("lse2", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_LUT
-    ("lut", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("lut", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_MOPS
-    ("mops", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("mops", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_MTE & FEAT_MTE2
-    ("mte", Stable, &[]),
+    ("mte", STABLE, &[]),
     // FEAT_AdvSimd & FEAT_FP
-    ("neon", Stable, &[]),
+    ("neon", STABLE, &[]),
     // FEAT_PAUTH (address authentication)
-    ("paca", Stable, &[]),
+    ("paca", STABLE, &[]),
     // FEAT_PAUTH (generic authentication)
-    ("pacg", Stable, &[]),
+    ("pacg", STABLE, &[]),
     // FEAT_PAN
-    ("pan", Stable, &[]),
+    ("pan", STABLE, &[]),
     // FEAT_PAuth_LR
-    ("pauth-lr", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("pauth-lr", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_PMUv3
-    ("pmuv3", Stable, &[]),
+    ("pmuv3", STABLE, &[]),
     // FEAT_RNG
-    ("rand", Stable, &[]),
+    ("rand", STABLE, &[]),
     // FEAT_RAS & FEAT_RASv1p1
-    ("ras", Stable, &[]),
+    ("ras", STABLE, &[]),
     // FEAT_LRCPC
-    ("rcpc", Stable, &[]),
+    ("rcpc", STABLE, &[]),
     // FEAT_LRCPC2
-    ("rcpc2", Stable, &["rcpc"]),
+    ("rcpc2", STABLE, &["rcpc"]),
     // FEAT_LRCPC3
-    ("rcpc3", Unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
+    ("rcpc3", unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
     // FEAT_RDM
-    ("rdm", Stable, &["neon"]),
+    ("rdm", STABLE, &["neon"]),
     // This is needed for inline assembly, but shouldn't be stabilized as-is
     // since it should be enabled globally using -Zfixed-x18, not
     // #[target_feature].
     // Note that cfg(target_feature = "reserve-x18") is currently not set for
     // targets that reserve x18 by default.
-    ("reserve-x18", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("reserve-x18", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_SB
-    ("sb", Stable, &[]),
+    ("sb", STABLE, &[]),
     // FEAT_SHA1 & FEAT_SHA256
-    ("sha2", Stable, &["neon"]),
+    ("sha2", STABLE, &["neon"]),
     // FEAT_SHA512 & FEAT_SHA3
-    ("sha3", Stable, &["sha2"]),
+    ("sha3", STABLE, &["sha2"]),
     // FEAT_SM3 & FEAT_SM4
-    ("sm4", Stable, &["neon"]),
+    ("sm4", STABLE, &["neon"]),
     // FEAT_SME
-    ("sme", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
+    ("sme", unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
     // FEAT_SME_B16B16
-    ("sme-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16", "sme2", "sve-b16b16"]),
+    ("sme-b16b16", unstable(sym::aarch64_unstable_target_feature), &["bf16", "sme2", "sve-b16b16"]),
     // FEAT_SME_F16F16
-    ("sme-f16f16", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
+    ("sme-f16f16", unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
     // FEAT_SME_F64F64
-    ("sme-f64f64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme-f64f64", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME_F8F16
-    ("sme-f8f16", Unstable(sym::aarch64_unstable_target_feature), &["sme-f8f32"]),
+    ("sme-f8f16", unstable(sym::aarch64_unstable_target_feature), &["sme-f8f32"]),
     // FEAT_SME_F8F32
-    ("sme-f8f32", Unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
+    ("sme-f8f32", unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
     // FEAT_SME_FA64
-    ("sme-fa64", Unstable(sym::aarch64_unstable_target_feature), &["sme", "sve2"]),
+    ("sme-fa64", unstable(sym::aarch64_unstable_target_feature), &["sme", "sve2"]),
     // FEAT_SME_I16I64
-    ("sme-i16i64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme-i16i64", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME_LUTv2
-    ("sme-lutv2", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("sme-lutv2", unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_SME2
-    ("sme2", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme2", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME2p1
-    ("sme2p1", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
+    ("sme2p1", unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
     // FEAT_SPE
-    ("spe", Stable, &[]),
+    ("spe", STABLE, &[]),
     // FEAT_SSBS & FEAT_SSBS2
-    ("ssbs", Stable, &[]),
+    ("ssbs", STABLE, &[]),
     // FEAT_SSVE_FP8FDOT2
-    ("ssve-fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8dot4"]),
+    ("ssve-fp8dot2", unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8dot4"]),
     // FEAT_SSVE_FP8FDOT4
-    ("ssve-fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8fma"]),
+    ("ssve-fp8dot4", unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8fma"]),
     // FEAT_SSVE_FP8FMA
-    ("ssve-fp8fma", Unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
+    ("ssve-fp8fma", unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
     // FEAT_SVE
     // It was decided that SVE requires Neon: https://github.com/rust-lang/rust/pull/91608
     //
@@ -281,46 +341,46 @@ const AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // exist together: https://developer.arm.com/documentation/102340/0100/New-features-in-SVE2
     //
     // "For backwards compatibility, Neon and VFP are required in the latest architectures."
-    ("sve", Stable, &["neon"]),
+    ("sve", STABLE, &["neon"]),
     // FEAT_SVE_B16B16 (SVE or SME Z-targeting instructions)
-    ("sve-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
+    ("sve-b16b16", unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
     // FEAT_SVE2
-    ("sve2", Stable, &["sve"]),
+    ("sve2", STABLE, &["sve"]),
     // FEAT_SVE_AES & FEAT_SVE_PMULL128
-    ("sve2-aes", Stable, &["sve2", "aes"]),
+    ("sve2-aes", STABLE, &["sve2", "aes"]),
     // FEAT_SVE2_BitPerm
-    ("sve2-bitperm", Stable, &["sve2"]),
+    ("sve2-bitperm", STABLE, &["sve2"]),
     // FEAT_SVE2_SHA3
-    ("sve2-sha3", Stable, &["sve2", "sha3"]),
+    ("sve2-sha3", STABLE, &["sve2", "sha3"]),
     // FEAT_SVE2_SM4
-    ("sve2-sm4", Stable, &["sve2", "sm4"]),
+    ("sve2-sm4", STABLE, &["sve2", "sm4"]),
     // FEAT_SVE2p1
-    ("sve2p1", Unstable(sym::aarch64_unstable_target_feature), &["sve2"]),
+    ("sve2p1", unstable(sym::aarch64_unstable_target_feature), &["sve2"]),
     // FEAT_TME
-    ("tme", Stable, &[]),
-    ("v8.1a", Unstable(sym::aarch64_ver_target_feature), &[
+    ("tme", STABLE, &[]),
+    ("v8.1a", unstable(sym::aarch64_ver_target_feature), &[
         "crc", "lse", "rdm", "pan", "lor", "vh",
     ]),
-    ("v8.2a", Unstable(sym::aarch64_ver_target_feature), &["v8.1a", "ras", "dpb"]),
-    ("v8.3a", Unstable(sym::aarch64_ver_target_feature), &[
+    ("v8.2a", unstable(sym::aarch64_ver_target_feature), &["v8.1a", "ras", "dpb"]),
+    ("v8.3a", unstable(sym::aarch64_ver_target_feature), &[
         "v8.2a", "rcpc", "paca", "pacg", "jsconv",
     ]),
-    ("v8.4a", Unstable(sym::aarch64_ver_target_feature), &["v8.3a", "dotprod", "dit", "flagm"]),
-    ("v8.5a", Unstable(sym::aarch64_ver_target_feature), &["v8.4a", "ssbs", "sb", "dpb2", "bti"]),
-    ("v8.6a", Unstable(sym::aarch64_ver_target_feature), &["v8.5a", "bf16", "i8mm"]),
-    ("v8.7a", Unstable(sym::aarch64_ver_target_feature), &["v8.6a", "wfxt"]),
-    ("v8.8a", Unstable(sym::aarch64_ver_target_feature), &["v8.7a", "hbc", "mops"]),
-    ("v8.9a", Unstable(sym::aarch64_ver_target_feature), &["v8.8a", "cssc"]),
-    ("v9.1a", Unstable(sym::aarch64_ver_target_feature), &["v9a", "v8.6a"]),
-    ("v9.2a", Unstable(sym::aarch64_ver_target_feature), &["v9.1a", "v8.7a"]),
-    ("v9.3a", Unstable(sym::aarch64_ver_target_feature), &["v9.2a", "v8.8a"]),
-    ("v9.4a", Unstable(sym::aarch64_ver_target_feature), &["v9.3a", "v8.9a"]),
-    ("v9.5a", Unstable(sym::aarch64_ver_target_feature), &["v9.4a"]),
-    ("v9a", Unstable(sym::aarch64_ver_target_feature), &["v8.5a", "sve2"]),
+    ("v8.4a", unstable(sym::aarch64_ver_target_feature), &["v8.3a", "dotprod", "dit", "flagm"]),
+    ("v8.5a", unstable(sym::aarch64_ver_target_feature), &["v8.4a", "ssbs", "sb", "dpb2", "bti"]),
+    ("v8.6a", unstable(sym::aarch64_ver_target_feature), &["v8.5a", "bf16", "i8mm"]),
+    ("v8.7a", unstable(sym::aarch64_ver_target_feature), &["v8.6a", "wfxt"]),
+    ("v8.8a", unstable(sym::aarch64_ver_target_feature), &["v8.7a", "hbc", "mops"]),
+    ("v8.9a", unstable(sym::aarch64_ver_target_feature), &["v8.8a", "cssc"]),
+    ("v9.1a", unstable(sym::aarch64_ver_target_feature), &["v9a", "v8.6a"]),
+    ("v9.2a", unstable(sym::aarch64_ver_target_feature), &["v9.1a", "v8.7a"]),
+    ("v9.3a", unstable(sym::aarch64_ver_target_feature), &["v9.2a", "v8.8a"]),
+    ("v9.4a", unstable(sym::aarch64_ver_target_feature), &["v9.3a", "v8.9a"]),
+    ("v9.5a", unstable(sym::aarch64_ver_target_feature), &["v9.4a"]),
+    ("v9a", unstable(sym::aarch64_ver_target_feature), &["v8.5a", "sve2"]),
     // FEAT_VHE
-    ("vh", Stable, &[]),
+    ("vh", STABLE, &[]),
     // FEAT_WFxT
-    ("wfxt", Unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("wfxt", unstable(sym::aarch64_unstable_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
@@ -328,241 +388,241 @@ const AARCH64_TIED_FEATURES: &[&[&str]] = &[
     &["paca", "pacg"], // Together these represent `pauth` in LLVM
 ];
 
-const X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const X86_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("adx", Stable, &[]),
-    ("aes", Stable, &["sse2"]),
-    ("amx-bf16", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
-    ("amx-complex", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
-    ("amx-fp16", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
-    ("amx-int8", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
-    ("amx-tile", Unstable(sym::x86_amx_intrinsics), &[]),
-    ("avx", Stable, &["sse4.2"]),
-    ("avx2", Stable, &["avx"]),
-    ("avx512bf16", Unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512bitalg", Unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512bw", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512cd", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512dq", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512f", Unstable(sym::avx512_target_feature), &["avx2", "fma", "f16c"]),
-    ("avx512fp16", Unstable(sym::avx512_target_feature), &["avx512bw", "avx512vl", "avx512dq"]),
-    ("avx512ifma", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vbmi", Unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512vbmi2", Unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512vl", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vnni", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vp2intersect", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vpopcntdq", Unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avxifma", Unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxneconvert", Unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnni", Unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnniint16", Unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnniint8", Unstable(sym::avx512_target_feature), &["avx2"]),
-    ("bmi1", Stable, &[]),
-    ("bmi2", Stable, &[]),
-    ("cmpxchg16b", Stable, &[]),
-    ("ermsb", Unstable(sym::ermsb_target_feature), &[]),
-    ("f16c", Stable, &["avx"]),
-    ("fma", Stable, &["avx"]),
-    ("fxsr", Stable, &[]),
-    ("gfni", Unstable(sym::avx512_target_feature), &["sse2"]),
-    ("lahfsahf", Unstable(sym::lahfsahf_target_feature), &[]),
-    ("lzcnt", Stable, &[]),
-    ("movbe", Stable, &[]),
-    ("pclmulqdq", Stable, &["sse2"]),
-    ("popcnt", Stable, &[]),
-    ("prfchw", Unstable(sym::prfchw_target_feature), &[]),
-    ("rdrand", Stable, &[]),
-    ("rdseed", Stable, &[]),
-    ("rtm", Unstable(sym::rtm_target_feature), &[]),
-    ("sha", Stable, &["sse2"]),
-    ("sha512", Unstable(sym::sha512_sm_x86), &["avx2"]),
-    ("sm3", Unstable(sym::sha512_sm_x86), &["avx"]),
-    ("sm4", Unstable(sym::sha512_sm_x86), &["avx2"]),
-    ("soft-float", Forbidden { reason: "unsound because it changes float ABI" }, &[]),
-    ("sse", Stable, &[]),
-    ("sse2", Stable, &["sse"]),
-    ("sse3", Stable, &["sse2"]),
-    ("sse4.1", Stable, &["ssse3"]),
-    ("sse4.2", Stable, &["sse4.1"]),
-    ("sse4a", Unstable(sym::sse4a_target_feature), &["sse3"]),
-    ("ssse3", Stable, &["sse3"]),
-    ("tbm", Unstable(sym::tbm_target_feature), &[]),
-    ("vaes", Unstable(sym::avx512_target_feature), &["avx2", "aes"]),
-    ("vpclmulqdq", Unstable(sym::avx512_target_feature), &["avx", "pclmulqdq"]),
-    ("xop", Unstable(sym::xop_target_feature), &[/*"fma4", */ "avx", "sse4a"]),
-    ("xsave", Stable, &[]),
-    ("xsavec", Stable, &["xsave"]),
-    ("xsaveopt", Stable, &["xsave"]),
-    ("xsaves", Stable, &["xsave"]),
+    ("adx", STABLE, &[]),
+    ("aes", STABLE, &["sse2"]),
+    ("amx-bf16", unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
+    ("amx-complex", unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
+    ("amx-fp16", unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
+    ("amx-int8", unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
+    ("amx-tile", unstable(sym::x86_amx_intrinsics), &[]),
+    ("avx", STABLE, &["sse4.2"]),
+    ("avx2", STABLE, &["avx"]),
+    ("avx512bf16", unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512bitalg", unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512bw", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512cd", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512dq", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512f", unstable(sym::avx512_target_feature), &["avx2", "fma", "f16c"]),
+    ("avx512fp16", unstable(sym::avx512_target_feature), &["avx512bw", "avx512vl", "avx512dq"]),
+    ("avx512ifma", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vbmi", unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512vbmi2", unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512vl", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vnni", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vp2intersect", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vpopcntdq", unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avxifma", unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxneconvert", unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnni", unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnniint16", unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnniint8", unstable(sym::avx512_target_feature), &["avx2"]),
+    ("bmi1", STABLE, &[]),
+    ("bmi2", STABLE, &[]),
+    ("cmpxchg16b", STABLE, &[]),
+    ("ermsb", unstable(sym::ermsb_target_feature), &[]),
+    ("f16c", STABLE, &["avx"]),
+    ("fma", STABLE, &["avx"]),
+    ("fxsr", STABLE, &[]),
+    ("gfni", unstable(sym::avx512_target_feature), &["sse2"]),
+    ("lahfsahf", unstable(sym::lahfsahf_target_feature), &[]),
+    ("lzcnt", STABLE, &[]),
+    ("movbe", STABLE, &[]),
+    ("pclmulqdq", STABLE, &["sse2"]),
+    ("popcnt", STABLE, &[]),
+    ("prfchw", unstable(sym::prfchw_target_feature), &[]),
+    ("rdrand", STABLE, &[]),
+    ("rdseed", STABLE, &[]),
+    ("rtm", unstable(sym::rtm_target_feature), &[]),
+    ("sha", STABLE, &["sse2"]),
+    ("sha512", unstable(sym::sha512_sm_x86), &["avx2"]),
+    ("sm3", unstable(sym::sha512_sm_x86), &["avx"]),
+    ("sm4", unstable(sym::sha512_sm_x86), &["avx2"]),
+    ("soft-float", Stability::Forbidden { reason: "unsound because it changes float ABI" }, &[]),
+    ("sse", STABLE, &[]),
+    ("sse2", STABLE, &["sse"]),
+    ("sse3", STABLE, &["sse2"]),
+    ("sse4.1", STABLE, &["ssse3"]),
+    ("sse4.2", STABLE, &["sse4.1"]),
+    ("sse4a", unstable(sym::sse4a_target_feature), &["sse3"]),
+    ("ssse3", STABLE, &["sse3"]),
+    ("tbm", unstable(sym::tbm_target_feature), &[]),
+    ("vaes", unstable(sym::avx512_target_feature), &["avx2", "aes"]),
+    ("vpclmulqdq", unstable(sym::avx512_target_feature), &["avx", "pclmulqdq"]),
+    ("xop", unstable(sym::xop_target_feature), &[/*"fma4", */ "avx", "sse4a"]),
+    ("xsave", STABLE, &[]),
+    ("xsavec", STABLE, &["xsave"]),
+    ("xsaveopt", STABLE, &["xsave"]),
+    ("xsaves", STABLE, &["xsave"]),
     // tidy-alphabetical-end
     // FIXME: need to also forbid turning off `x87` on hardfloat targets
 ];
 
-const HEXAGON_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const HEXAGON_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("hvx", Unstable(sym::hexagon_target_feature), &[]),
-    ("hvx-length128b", Unstable(sym::hexagon_target_feature), &["hvx"]),
+    ("hvx", unstable(sym::hexagon_target_feature), &[]),
+    ("hvx-length128b", unstable(sym::hexagon_target_feature), &["hvx"]),
     // tidy-alphabetical-end
 ];
 
-const POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const POWERPC_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("altivec", Unstable(sym::powerpc_target_feature), &[]),
-    ("partword-atomics", Unstable(sym::powerpc_target_feature), &[]),
-    ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]),
-    ("power8-altivec", Unstable(sym::powerpc_target_feature), &["altivec"]),
-    ("power8-crypto", Unstable(sym::powerpc_target_feature), &["power8-altivec"]),
-    ("power8-vector", Unstable(sym::powerpc_target_feature), &["vsx", "power8-altivec"]),
-    ("power9-altivec", Unstable(sym::powerpc_target_feature), &["power8-altivec"]),
-    ("power9-vector", Unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]),
-    ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]),
-    ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]),
+    ("altivec", unstable(sym::powerpc_target_feature), &[]),
+    ("partword-atomics", unstable(sym::powerpc_target_feature), &[]),
+    ("power10-vector", unstable(sym::powerpc_target_feature), &["power9-vector"]),
+    ("power8-altivec", unstable(sym::powerpc_target_feature), &["altivec"]),
+    ("power8-crypto", unstable(sym::powerpc_target_feature), &["power8-altivec"]),
+    ("power8-vector", unstable(sym::powerpc_target_feature), &["vsx", "power8-altivec"]),
+    ("power9-altivec", unstable(sym::powerpc_target_feature), &["power8-altivec"]),
+    ("power9-vector", unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]),
+    ("quadword-atomics", unstable(sym::powerpc_target_feature), &[]),
+    ("vsx", unstable(sym::powerpc_target_feature), &["altivec"]),
     // tidy-alphabetical-end
 ];
 
-const MIPS_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const MIPS_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("fp64", Unstable(sym::mips_target_feature), &[]),
-    ("msa", Unstable(sym::mips_target_feature), &[]),
-    ("virt", Unstable(sym::mips_target_feature), &[]),
+    ("fp64", unstable(sym::mips_target_feature), &[]),
+    ("msa", unstable(sym::mips_target_feature), &[]),
+    ("virt", unstable(sym::mips_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const RISCV_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("a", Stable, &["zaamo", "zalrsc"]),
-    ("c", Stable, &[]),
-    ("d", Unstable(sym::riscv_target_feature), &["f"]),
-    ("e", Unstable(sym::riscv_target_feature), &[]),
-    ("f", Unstable(sym::riscv_target_feature), &[]),
-    ("m", Stable, &[]),
-    ("relax", Unstable(sym::riscv_target_feature), &[]),
-    ("unaligned-scalar-mem", Unstable(sym::riscv_target_feature), &[]),
-    ("v", Unstable(sym::riscv_target_feature), &[]),
-    ("zaamo", Unstable(sym::riscv_target_feature), &[]),
-    ("zabha", Unstable(sym::riscv_target_feature), &["zaamo"]),
-    ("zalrsc", Unstable(sym::riscv_target_feature), &[]),
-    ("zba", Stable, &[]),
-    ("zbb", Stable, &[]),
-    ("zbc", Stable, &[]),
-    ("zbkb", Stable, &[]),
-    ("zbkc", Stable, &[]),
-    ("zbkx", Stable, &[]),
-    ("zbs", Stable, &[]),
-    ("zdinx", Unstable(sym::riscv_target_feature), &["zfinx"]),
-    ("zfh", Unstable(sym::riscv_target_feature), &["zfhmin"]),
-    ("zfhmin", Unstable(sym::riscv_target_feature), &["f"]),
-    ("zfinx", Unstable(sym::riscv_target_feature), &[]),
-    ("zhinx", Unstable(sym::riscv_target_feature), &["zhinxmin"]),
-    ("zhinxmin", Unstable(sym::riscv_target_feature), &["zfinx"]),
-    ("zk", Stable, &["zkn", "zkr", "zkt"]),
-    ("zkn", Stable, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]),
-    ("zknd", Stable, &[]),
-    ("zkne", Stable, &[]),
-    ("zknh", Stable, &[]),
-    ("zkr", Stable, &[]),
-    ("zks", Stable, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]),
-    ("zksed", Stable, &[]),
-    ("zksh", Stable, &[]),
-    ("zkt", Stable, &[]),
+    ("a", STABLE, &["zaamo", "zalrsc"]),
+    ("c", STABLE, &[]),
+    ("d", unstable(sym::riscv_target_feature), &["f"]),
+    ("e", unstable(sym::riscv_target_feature), &[]),
+    ("f", unstable(sym::riscv_target_feature), &[]),
+    ("m", STABLE, &[]),
+    ("relax", unstable(sym::riscv_target_feature), &[]),
+    ("unaligned-scalar-mem", unstable(sym::riscv_target_feature), &[]),
+    ("v", unstable(sym::riscv_target_feature), &[]),
+    ("zaamo", unstable(sym::riscv_target_feature), &[]),
+    ("zabha", unstable(sym::riscv_target_feature), &["zaamo"]),
+    ("zalrsc", unstable(sym::riscv_target_feature), &[]),
+    ("zba", STABLE, &[]),
+    ("zbb", STABLE, &[]),
+    ("zbc", STABLE, &[]),
+    ("zbkb", STABLE, &[]),
+    ("zbkc", STABLE, &[]),
+    ("zbkx", STABLE, &[]),
+    ("zbs", STABLE, &[]),
+    ("zdinx", unstable(sym::riscv_target_feature), &["zfinx"]),
+    ("zfh", unstable(sym::riscv_target_feature), &["zfhmin"]),
+    ("zfhmin", unstable(sym::riscv_target_feature), &["f"]),
+    ("zfinx", unstable(sym::riscv_target_feature), &[]),
+    ("zhinx", unstable(sym::riscv_target_feature), &["zhinxmin"]),
+    ("zhinxmin", unstable(sym::riscv_target_feature), &["zfinx"]),
+    ("zk", STABLE, &["zkn", "zkr", "zkt"]),
+    ("zkn", STABLE, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]),
+    ("zknd", STABLE, &[]),
+    ("zkne", STABLE, &[]),
+    ("zknh", STABLE, &[]),
+    ("zkr", STABLE, &[]),
+    ("zks", STABLE, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]),
+    ("zksed", STABLE, &[]),
+    ("zksh", STABLE, &[]),
+    ("zkt", STABLE, &[]),
     // tidy-alphabetical-end
 ];
 
-const WASM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const WASM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("atomics", Unstable(sym::wasm_target_feature), &[]),
-    ("bulk-memory", Stable, &[]),
-    ("exception-handling", Unstable(sym::wasm_target_feature), &[]),
-    ("extended-const", Stable, &[]),
-    ("multivalue", Stable, &[]),
-    ("mutable-globals", Stable, &[]),
-    ("nontrapping-fptoint", Stable, &[]),
-    ("reference-types", Stable, &[]),
-    ("relaxed-simd", Stable, &["simd128"]),
-    ("sign-ext", Stable, &[]),
-    ("simd128", Stable, &[]),
-    ("tail-call", Stable, &[]),
-    ("wide-arithmetic", Unstable(sym::wasm_target_feature), &[]),
+    ("atomics", unstable(sym::wasm_target_feature), &[]),
+    ("bulk-memory", STABLE, &[]),
+    ("exception-handling", unstable(sym::wasm_target_feature), &[]),
+    ("extended-const", STABLE, &[]),
+    ("multivalue", STABLE, &[]),
+    ("mutable-globals", STABLE, &[]),
+    ("nontrapping-fptoint", STABLE, &[]),
+    ("reference-types", STABLE, &[]),
+    ("relaxed-simd", STABLE, &["simd128"]),
+    ("sign-ext", STABLE, &[]),
+    ("simd128", STABLE, &[]),
+    ("tail-call", STABLE, &[]),
+    ("wide-arithmetic", unstable(sym::wasm_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] =
-    &[("alu32", Unstable(sym::bpf_target_feature), &[])];
+const BPF_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] =
+    &[("alu32", unstable(sym::bpf_target_feature), &[])];
 
-const CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const CSKY_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("10e60", Unstable(sym::csky_target_feature), &["7e10"]),
-    ("2e3", Unstable(sym::csky_target_feature), &["e2"]),
-    ("3e3r1", Unstable(sym::csky_target_feature), &[]),
-    ("3e3r2", Unstable(sym::csky_target_feature), &["3e3r1", "doloop"]),
-    ("3e3r3", Unstable(sym::csky_target_feature), &["doloop"]),
-    ("3e7", Unstable(sym::csky_target_feature), &["2e3"]),
-    ("7e10", Unstable(sym::csky_target_feature), &["3e7"]),
-    ("cache", Unstable(sym::csky_target_feature), &[]),
-    ("doloop", Unstable(sym::csky_target_feature), &[]),
-    ("dsp1e2", Unstable(sym::csky_target_feature), &[]),
-    ("dspe60", Unstable(sym::csky_target_feature), &[]),
-    ("e1", Unstable(sym::csky_target_feature), &["elrw"]),
-    ("e2", Unstable(sym::csky_target_feature), &["e2"]),
-    ("edsp", Unstable(sym::csky_target_feature), &[]),
-    ("elrw", Unstable(sym::csky_target_feature), &[]),
-    ("float1e2", Unstable(sym::csky_target_feature), &[]),
-    ("float1e3", Unstable(sym::csky_target_feature), &[]),
-    ("float3e4", Unstable(sym::csky_target_feature), &[]),
-    ("float7e60", Unstable(sym::csky_target_feature), &[]),
-    ("floate1", Unstable(sym::csky_target_feature), &[]),
-    ("hard-tp", Unstable(sym::csky_target_feature), &[]),
-    ("high-registers", Unstable(sym::csky_target_feature), &[]),
-    ("hwdiv", Unstable(sym::csky_target_feature), &[]),
-    ("mp", Unstable(sym::csky_target_feature), &["2e3"]),
-    ("mp1e2", Unstable(sym::csky_target_feature), &["3e7"]),
-    ("nvic", Unstable(sym::csky_target_feature), &[]),
-    ("trust", Unstable(sym::csky_target_feature), &[]),
-    ("vdsp2e60f", Unstable(sym::csky_target_feature), &[]),
-    ("vdspv1", Unstable(sym::csky_target_feature), &[]),
-    ("vdspv2", Unstable(sym::csky_target_feature), &[]),
+    ("10e60", unstable(sym::csky_target_feature), &["7e10"]),
+    ("2e3", unstable(sym::csky_target_feature), &["e2"]),
+    ("3e3r1", unstable(sym::csky_target_feature), &[]),
+    ("3e3r2", unstable(sym::csky_target_feature), &["3e3r1", "doloop"]),
+    ("3e3r3", unstable(sym::csky_target_feature), &["doloop"]),
+    ("3e7", unstable(sym::csky_target_feature), &["2e3"]),
+    ("7e10", unstable(sym::csky_target_feature), &["3e7"]),
+    ("cache", unstable(sym::csky_target_feature), &[]),
+    ("doloop", unstable(sym::csky_target_feature), &[]),
+    ("dsp1e2", unstable(sym::csky_target_feature), &[]),
+    ("dspe60", unstable(sym::csky_target_feature), &[]),
+    ("e1", unstable(sym::csky_target_feature), &["elrw"]),
+    ("e2", unstable(sym::csky_target_feature), &["e2"]),
+    ("edsp", unstable(sym::csky_target_feature), &[]),
+    ("elrw", unstable(sym::csky_target_feature), &[]),
+    ("float1e2", unstable(sym::csky_target_feature), &[]),
+    ("float1e3", unstable(sym::csky_target_feature), &[]),
+    ("float3e4", unstable(sym::csky_target_feature), &[]),
+    ("float7e60", unstable(sym::csky_target_feature), &[]),
+    ("floate1", unstable(sym::csky_target_feature), &[]),
+    ("hard-tp", unstable(sym::csky_target_feature), &[]),
+    ("high-registers", unstable(sym::csky_target_feature), &[]),
+    ("hwdiv", unstable(sym::csky_target_feature), &[]),
+    ("mp", unstable(sym::csky_target_feature), &["2e3"]),
+    ("mp1e2", unstable(sym::csky_target_feature), &["3e7"]),
+    ("nvic", unstable(sym::csky_target_feature), &[]),
+    ("trust", unstable(sym::csky_target_feature), &[]),
+    ("vdsp2e60f", unstable(sym::csky_target_feature), &[]),
+    ("vdspv1", unstable(sym::csky_target_feature), &[]),
+    ("vdspv2", unstable(sym::csky_target_feature), &[]),
     // tidy-alphabetical-end
     //fpu
     // tidy-alphabetical-start
-    ("fdivdu", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv2_df", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv2_sf", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv3_df", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv3_hf", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv3_hi", Unstable(sym::csky_target_feature), &[]),
-    ("fpuv3_sf", Unstable(sym::csky_target_feature), &[]),
-    ("hard-float", Unstable(sym::csky_target_feature), &[]),
-    ("hard-float-abi", Unstable(sym::csky_target_feature), &[]),
+    ("fdivdu", unstable(sym::csky_target_feature), &[]),
+    ("fpuv2_df", unstable(sym::csky_target_feature), &[]),
+    ("fpuv2_sf", unstable(sym::csky_target_feature), &[]),
+    ("fpuv3_df", unstable(sym::csky_target_feature), &[]),
+    ("fpuv3_hf", unstable(sym::csky_target_feature), &[]),
+    ("fpuv3_hi", unstable(sym::csky_target_feature), &[]),
+    ("fpuv3_sf", unstable(sym::csky_target_feature), &[]),
+    ("hard-float", unstable(sym::csky_target_feature), &[]),
+    ("hard-float-abi", unstable(sym::csky_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const LOONGARCH_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const LOONGARCH_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("d", Unstable(sym::loongarch_target_feature), &["f"]),
-    ("f", Unstable(sym::loongarch_target_feature), &[]),
-    ("frecipe", Unstable(sym::loongarch_target_feature), &[]),
-    ("lasx", Unstable(sym::loongarch_target_feature), &["lsx"]),
-    ("lbt", Unstable(sym::loongarch_target_feature), &[]),
-    ("lsx", Unstable(sym::loongarch_target_feature), &["d"]),
-    ("lvz", Unstable(sym::loongarch_target_feature), &[]),
-    ("relax", Unstable(sym::loongarch_target_feature), &[]),
-    ("ual", Unstable(sym::loongarch_target_feature), &[]),
+    ("d", unstable(sym::loongarch_target_feature), &["f"]),
+    ("f", unstable(sym::loongarch_target_feature), &[]),
+    ("frecipe", unstable(sym::loongarch_target_feature), &[]),
+    ("lasx", unstable(sym::loongarch_target_feature), &["lsx"]),
+    ("lbt", unstable(sym::loongarch_target_feature), &[]),
+    ("lsx", unstable(sym::loongarch_target_feature), &["d"]),
+    ("lvz", unstable(sym::loongarch_target_feature), &[]),
+    ("relax", unstable(sym::loongarch_target_feature), &[]),
+    ("ual", unstable(sym::loongarch_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const IBMZ_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("backchain", Unstable(sym::s390x_target_feature), &[]),
-    ("vector", Unstable(sym::s390x_target_feature), &[]),
+    ("backchain", unstable(sym::s390x_target_feature), &[]),
+    ("vector", unstable(sym::s390x_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const SPARC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const SPARC_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("leoncasa", Unstable(sym::sparc_target_feature), &[]),
-    ("v8plus", Unstable(sym::sparc_target_feature), &[]),
-    ("v9", Unstable(sym::sparc_target_feature), &[]),
+    ("leoncasa", unstable(sym::sparc_target_feature), &[]),
+    ("v8plus", unstable(sym::sparc_target_feature), &[]),
+    ("v9", unstable(sym::sparc_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
@@ -570,7 +630,7 @@ const SPARC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
 /// primitives may be documented.
 ///
 /// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator!
-pub fn all_rust_features() -> impl Iterator<Item = (&'static str, Stability)> {
+pub fn all_rust_features() -> impl Iterator<Item = (&'static str, StabilityUncomputed)> {
     std::iter::empty()
         .chain(ARM_FEATURES.iter())
         .chain(AARCH64_FEATURES.iter())
@@ -612,8 +672,10 @@ const HEXAGON_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] =
 const MIPS_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "msa")];
 const CSKY_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "vdspv1")];
 
-impl super::spec::Target {
-    pub fn rust_target_features(&self) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
+impl Target {
+    pub fn rust_target_features(
+        &self,
+    ) -> &'static [(&'static str, StabilityUncomputed, ImpliedFeatures)] {
         match &*self.arch {
             "arm" => ARM_FEATURES,
             "aarch64" | "arm64ec" => AARCH64_FEATURES,