about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-10-06 19:59:19 +0200
committerRalf Jung <post@ralfj.de>2024-10-25 20:31:40 +0200
commita0215d8e46aab41219dea0bb1cbaaf97dafe2f89 (patch)
treecb98c6fb900deceea7aa9f2d08455de383c45d02
parent45089ec19ebebec88bace6ec237244ff0eaa7ad3 (diff)
downloadrust-a0215d8e46aab41219dea0bb1cbaaf97dafe2f89.tar.gz
rust-a0215d8e46aab41219dea0bb1cbaaf97dafe2f89.zip
Re-do recursive const stability checks
Fundamentally, we have *three* disjoint categories of functions:
1. const-stable functions
2. private/unstable functions that are meant to be callable from const-stable functions
3. functions that can make use of unstable const features

This PR implements the following system:
- `#[rustc_const_stable]` puts functions in the first category. It may only be applied to `#[stable]` functions.
- `#[rustc_const_unstable]` by default puts functions in the third category. The new attribute `#[rustc_const_stable_indirect]` can be added to such a function to move it into the second category.
- `const fn` without a const stability marker are in the second category if they are still unstable. They automatically inherit the feature gate for regular calls, it can now also be used for const-calls.

Also, several holes in recursive const stability checking are being closed.
There's still one potential hole that is hard to avoid, which is when MIR
building automatically inserts calls to a particular function in stable
functions -- which happens in the panic machinery. Those need to *not* be
`rustc_const_unstable` (or manually get a `rustc_const_stable_indirect`) to be
sure they follow recursive const stability. But that's a fairly rare and special
case so IMO it's fine.

The net effect of this is that a `#[unstable]` or unmarked function can be
constified simply by marking it as `const fn`, and it will then be
const-callable from stable `const fn` and subject to recursive const stability
requirements. If it is publicly reachable (which implies it cannot be unmarked),
it will be const-unstable under the same feature gate. Only if the function ever
becomes `#[stable]` does it need a `#[rustc_const_unstable]` or
`#[rustc_const_stable]` marker to decide if this should also imply
const-stability.

Adding `#[rustc_const_unstable]` is only needed for (a) functions that need to
use unstable const lang features (including intrinsics), or (b) `#[stable]`
functions that are not yet intended to be const-stable. Adding
`#[rustc_const_stable]` is only needed for functions that are actually meant to
be directly callable from stable const code. `#[rustc_const_stable_indirect]` is
used to mark intrinsics as const-callable and for `#[rustc_const_unstable]`
functions that are actually called from other, exposed-on-stable `const fn`. No
other attributes are required.
-rw-r--r--compiler/rustc_attr/messages.ftl3
-rw-r--r--compiler/rustc_attr/src/builtin.rs78
-rw-r--r--compiler/rustc_attr/src/session_diagnostics.rs7
-rw-r--r--compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch8
-rw-r--r--compiler/rustc_const_eval/messages.ftl27
-rw-r--r--compiler/rustc_const_eval/src/check_consts/check.rs222
-rw-r--r--compiler/rustc_const_eval/src/check_consts/mod.rs60
-rw-r--r--compiler/rustc_const_eval/src/check_consts/ops.rs98
-rw-r--r--compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs2
-rw-r--r--compiler/rustc_const_eval/src/const_eval/fn_queries.rs19
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs2
-rw-r--r--compiler/rustc_const_eval/src/errors.rs47
-rw-r--r--compiler/rustc_expand/src/base.rs4
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs14
-rw-r--r--compiler/rustc_middle/src/ty/context.rs2
-rw-r--r--compiler/rustc_passes/messages.ftl8
-rw-r--r--compiler/rustc_passes/src/errors.rs8
-rw-r--r--compiler/rustc_passes/src/stability.rs118
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--library/alloc/src/raw_vec.rs6
-rw-r--r--library/core/src/alloc/layout.rs4
-rw-r--r--library/core/src/cell.rs1
-rw-r--r--library/core/src/cell/lazy.rs1
-rw-r--r--library/core/src/char/methods.rs2
-rw-r--r--library/core/src/ffi/c_str.rs6
-rw-r--r--library/core/src/fmt/mod.rs6
-rw-r--r--library/core/src/hint.rs2
-rw-r--r--library/core/src/intrinsics.rs181
-rw-r--r--library/core/src/iter/traits/collect.rs1
-rw-r--r--library/core/src/lib.rs6
-rw-r--r--library/core/src/net/ip_addr.rs1
-rw-r--r--library/core/src/num/f128.rs14
-rw-r--r--library/core/src/num/f16.rs14
-rw-r--r--library/core/src/num/mod.rs5
-rw-r--r--library/core/src/num/uint_macros.rs2
-rw-r--r--library/core/src/panic/panic_info.rs1
-rw-r--r--library/core/src/panicking.rs30
-rw-r--r--library/core/src/ptr/alignment.rs14
-rw-r--r--library/core/src/ptr/const_ptr.rs8
-rw-r--r--library/core/src/ptr/metadata.rs6
-rw-r--r--library/core/src/ptr/mod.rs9
-rw-r--r--library/core/src/ptr/mut_ptr.rs6
-rw-r--r--library/core/src/ptr/non_null.rs1
-rw-r--r--library/core/src/ptr/unique.rs1
-rw-r--r--library/core/src/slice/ascii.rs2
-rw-r--r--library/core/src/slice/index.rs4
-rw-r--r--library/core/src/slice/memchr.rs8
-rw-r--r--library/core/src/slice/mod.rs6
-rw-r--r--library/core/src/sync/atomic.rs23
-rw-r--r--library/core/src/sync/exclusive.rs3
-rw-r--r--library/core/src/task/wake.rs4
-rw-r--r--library/core/src/ub_checks.rs10
-rw-r--r--library/core/tests/lib.rs2
-rw-r--r--library/std/src/sys/sync/once/queue.rs2
-rw-r--r--library/std/src/sys/thread_local/key/racy.rs2
-rw-r--r--library/std/src/sys/thread_local/os.rs2
-rw-r--r--library/std/src/thread/local.rs2
-rw-r--r--src/librustdoc/clean/types.rs16
-rw-r--r--src/librustdoc/html/render/mod.rs4
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--tests/rustdoc/const-display.rs6
-rw-r--r--tests/ui/borrowck/issue-64453.rs1
-rw-r--r--tests/ui/borrowck/issue-64453.stderr13
-rw-r--r--tests/ui/consts/auxiliary/unstable_but_const_stable.rs13
-rw-r--r--tests/ui/consts/auxiliary/unstable_intrinsic.rs26
-rw-r--r--tests/ui/consts/const-eval/dont_promote_unstable_const_fn.rs4
-rw-r--r--tests/ui/consts/const-eval/dont_promote_unstable_const_fn.stderr14
-rw-r--r--tests/ui/consts/const-eval/simd/insert_extract.rs3
-rw-r--r--tests/ui/consts/const-unstable-intrinsic.rs76
-rw-r--r--tests/ui/consts/const-unstable-intrinsic.stderr127
-rw-r--r--tests/ui/consts/copy-intrinsic.rs2
-rw-r--r--tests/ui/consts/copy-intrinsic.stderr8
-rw-r--r--tests/ui/consts/intrinsic_without_const_stab.rs17
-rw-r--r--tests/ui/consts/intrinsic_without_const_stab.stderr11
-rw-r--r--tests/ui/consts/intrinsic_without_const_stab_fail.rs15
-rw-r--r--tests/ui/consts/intrinsic_without_const_stab_fail.stderr11
-rw-r--r--tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.rs34
-rw-r--r--tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.stderr108
-rw-r--r--tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.rs8
-rw-r--r--tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.stderr46
-rw-r--r--tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.rs8
-rw-r--r--tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.stderr46
-rw-r--r--tests/ui/consts/rustc-const-stability-require-const.rs31
-rw-r--r--tests/ui/consts/rustc-const-stability-require-const.stderr54
-rw-r--r--tests/ui/consts/unstable-const-stable.rs14
-rw-r--r--tests/ui/consts/unstable-const-stable.stderr43
-rw-r--r--tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr22
-rw-r--r--tests/ui/intrinsics/const-eval-select-stability.rs2
-rw-r--r--tests/ui/intrinsics/const-eval-select-stability.stderr13
-rw-r--r--tests/ui/stability-attribute/const-stability-attribute-implies-missing.rs1
-rw-r--r--tests/ui/stability-attribute/const-stability-attribute-implies-missing.stderr10
-rw-r--r--tests/ui/stability-attribute/missing-const-stability.rs13
-rw-r--r--tests/ui/stability-attribute/missing-const-stability.stderr10
-rw-r--r--tests/ui/target-feature/feature-hierarchy.rs1
-rw-r--r--tests/ui/target-feature/no-llvm-leaks.rs1
-rw-r--r--tests/ui/traits/const-traits/auxiliary/staged-api.rs9
-rw-r--r--tests/ui/traits/const-traits/effects/minicore.rs2
-rw-r--r--tests/ui/traits/const-traits/issue-79450.rs1
-rw-r--r--tests/ui/traits/const-traits/issue-79450.stderr2
-rw-r--r--tests/ui/traits/const-traits/staged-api.rs34
-rw-r--r--tests/ui/traits/const-traits/staged-api.stable.stderr56
-rw-r--r--tests/ui/traits/const-traits/staged-api.unstable.stderr102
102 files changed, 1511 insertions, 654 deletions
diff --git a/compiler/rustc_attr/messages.ftl b/compiler/rustc_attr/messages.ftl
index adabf18ca85..235ab7572c4 100644
--- a/compiler/rustc_attr/messages.ftl
+++ b/compiler/rustc_attr/messages.ftl
@@ -91,6 +91,9 @@ attr_non_ident_feature =
 attr_rustc_allowed_unstable_pairing =
     `rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute
 
+attr_rustc_const_stable_indirect_pairing =
+    `const_stable_indirect` attribute does not make sense on `rustc_const_stable` function, its behavior is already implied
+
 attr_rustc_promotable_pairing =
     `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute
 
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index 537d8e04531..6af75bc94bb 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -16,9 +16,9 @@ use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::lint::builtin::UNEXPECTED_CFGS;
 use rustc_session::parse::feature_err;
 use rustc_session::{RustcVersion, Session};
-use rustc_span::Span;
 use rustc_span::hygiene::Transparency;
 use rustc_span::symbol::{Symbol, kw, sym};
+use rustc_span::{DUMMY_SP, Span};
 
 use crate::fluent_generated;
 use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
@@ -92,7 +92,11 @@ impl Stability {
 #[derive(HashStable_Generic)]
 pub struct ConstStability {
     pub level: StabilityLevel,
-    pub feature: Symbol,
+    /// This can be `None` for functions that do not have an explicit const feature.
+    /// We still track them for recursive const stability checks.
+    pub feature: Option<Symbol>,
+    /// This is true iff the `const_stable_indirect` attribute is present.
+    pub const_stable_indirect: bool,
     /// whether the function has a `#[rustc_promotable]` attribute
     pub promotable: bool,
 }
@@ -268,17 +272,23 @@ pub fn find_stability(
 
 /// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
 /// attributes in `attrs`. Returns `None` if no stability attributes are found.
+///
+/// `is_const_fn` indicates whether this is a function marked as `const`. It will always
+/// be false for intrinsics in an `extern` block!
 pub fn find_const_stability(
     sess: &Session,
     attrs: &[Attribute],
     item_sp: Span,
+    is_const_fn: bool,
 ) -> Option<(ConstStability, Span)> {
     let mut const_stab: Option<(ConstStability, Span)> = None;
     let mut promotable = false;
+    let mut const_stable_indirect = None;
 
     for attr in attrs {
         match attr.name_or_empty() {
             sym::rustc_promotable => promotable = true,
+            sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
             sym::rustc_const_unstable => {
                 if const_stab.is_some() {
                     sess.dcx()
@@ -287,8 +297,15 @@ pub fn find_const_stability(
                 }
 
                 if let Some((feature, level)) = parse_unstability(sess, attr) {
-                    const_stab =
-                        Some((ConstStability { level, feature, promotable: false }, attr.span));
+                    const_stab = Some((
+                        ConstStability {
+                            level,
+                            feature: Some(feature),
+                            const_stable_indirect: false,
+                            promotable: false,
+                        },
+                        attr.span,
+                    ));
                 }
             }
             sym::rustc_const_stable => {
@@ -298,15 +315,22 @@ pub fn find_const_stability(
                     break;
                 }
                 if let Some((feature, level)) = parse_stability(sess, attr) {
-                    const_stab =
-                        Some((ConstStability { level, feature, promotable: false }, attr.span));
+                    const_stab = Some((
+                        ConstStability {
+                            level,
+                            feature: Some(feature),
+                            const_stable_indirect: false,
+                            promotable: false,
+                        },
+                        attr.span,
+                    ));
                 }
             }
             _ => {}
         }
     }
 
-    // Merge the const-unstable info into the stability info
+    // Merge promotable and not_exposed_on_stable into stability info
     if promotable {
         match &mut const_stab {
             Some((stab, _)) => stab.promotable = promotable,
@@ -317,6 +341,46 @@ pub fn find_const_stability(
             }
         }
     }
+    if const_stable_indirect.is_some() {
+        match &mut const_stab {
+            Some((stab, _)) => {
+                if stab.is_const_unstable() {
+                    stab.const_stable_indirect = true;
+                } else {
+                    _ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing {
+                        span: item_sp,
+                    })
+                }
+            }
+            _ => {
+                // We ignore the `#[rustc_const_stable_indirect]` here, it should be picked up by
+                // the `default_const_unstable` logic.
+            }
+        }
+    }
+    // Make sure if `const_stable_indirect` is present, that is recorded. Also make sure all `const
+    // fn` get *some* marker, since we are a staged_api crate and therefore will do recursive const
+    // stability checks for them. We need to do this because the default for whether an unmarked
+    // function enforces recursive stability differs between staged-api crates and force-unmarked
+    // crates: in force-unmarked crates, only functions *explicitly* marked `const_stable_indirect`
+    // enforce recursive stability. Therefore when `lookup_const_stability` is `None`, we have to
+    // assume the function does not have recursive stability. All functions that *do* have recursive
+    // stability must explicitly record this, and so that's what we do for all `const fn` in a
+    // staged_api crate.
+    if (is_const_fn || const_stable_indirect.is_some()) && const_stab.is_none() {
+        let c = ConstStability {
+            feature: None,
+            const_stable_indirect: const_stable_indirect.is_some(),
+            promotable: false,
+            level: StabilityLevel::Unstable {
+                reason: UnstableReason::Default,
+                issue: None,
+                is_soft: false,
+                implied_by: None,
+            },
+        };
+        const_stab = Some((c, const_stable_indirect.unwrap_or(DUMMY_SP)));
+    }
 
     const_stab
 }
diff --git a/compiler/rustc_attr/src/session_diagnostics.rs b/compiler/rustc_attr/src/session_diagnostics.rs
index 626840aa6a3..9d08a9f5754 100644
--- a/compiler/rustc_attr/src/session_diagnostics.rs
+++ b/compiler/rustc_attr/src/session_diagnostics.rs
@@ -319,6 +319,13 @@ pub(crate) struct RustcPromotablePairing {
 }
 
 #[derive(Diagnostic)]
+#[diag(attr_rustc_const_stable_indirect_pairing)]
+pub(crate) struct RustcConstStableIndirectPairing {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(attr_rustc_allowed_unstable_pairing, code = E0789)]
 pub(crate) struct RustcAllowedUnstablePairing {
     #[primary_span]
diff --git a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch
index 646928893e9..3c81b04c0ea 100644
--- a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch
+++ b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch
@@ -38,7 +38,7 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
 index d9de37e..8293fce 100644
 --- a/library/core/src/sync/atomic.rs
 +++ b/library/core/src/sync/atomic.rs
-@@ -2996,42 +2996,6 @@ atomic_int! {
+@@ -2996,44 +2996,6 @@ atomic_int! {
      8,
      u64 AtomicU64
  }
@@ -52,7 +52,8 @@ index d9de37e..8293fce 100644
 -    unstable(feature = "integer_atomics", issue = "99069"),
 -    unstable(feature = "integer_atomics", issue = "99069"),
 -    unstable(feature = "integer_atomics", issue = "99069"),
--    rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+-    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
+-    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
 -    cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"),
 -    "i128",
 -    "#![feature(integer_atomics)]\n\n",
@@ -70,7 +71,8 @@ index d9de37e..8293fce 100644
 -    unstable(feature = "integer_atomics", issue = "99069"),
 -    unstable(feature = "integer_atomics", issue = "99069"),
 -    unstable(feature = "integer_atomics", issue = "99069"),
--    rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+-    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
+-    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
 -    cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"),
 -    "u128",
 -    "#![feature(integer_atomics)]\n\n",
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl
index 24dbe688f36..3e4f83c8242 100644
--- a/compiler/rustc_const_eval/messages.ftl
+++ b/compiler/rustc_const_eval/messages.ftl
@@ -41,8 +41,6 @@ const_eval_const_context = {$kind ->
     *[other] {""}
 }
 
-const_eval_const_stable = const-stable functions can only call other const-stable functions
-
 const_eval_copy_nonoverlapping_overlapping =
     `copy_nonoverlapping` called on overlapping ranges
 
@@ -259,6 +257,9 @@ const_eval_non_const_fn_call =
 const_eval_non_const_impl =
     impl defined here, but it is not `const`
 
+const_eval_non_const_intrinsic =
+    cannot call non-const intrinsic `{$name}` in {const_eval_const_context}s
+
 const_eval_not_enough_caller_args =
     calling a function with fewer arguments than it requires
 
@@ -397,17 +398,29 @@ const_eval_uninhabited_enum_variant_read =
     read discriminant of an uninhabited enum variant
 const_eval_uninhabited_enum_variant_written =
     writing discriminant of an uninhabited enum variant
+
+const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
+    .help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
+const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable
+    .help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval)
+
 const_eval_unreachable = entering unreachable code
 const_eval_unreachable_unwind =
     unwinding past a stack frame that does not allow unwinding
 
 const_eval_unsized_local = unsized locals are not supported
 const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
-
-const_eval_unstable_in_stable =
-    const-stable function cannot use `#[feature({$gate})]`
-    .unstable_sugg = if the function is not (yet) meant to be stable, make this function unstably const
-    .bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval)
+const_eval_unstable_in_stable_exposed =
+    const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
+    .is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+    .unstable_sugg = if the {$is_function_call2 ->
+            [true] caller
+            *[false] function
+        } is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+    .bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+
+const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic
+    .help = add `#![feature({$feature})]` to the crate attributes to enable
 
 const_eval_unterminated_c_string =
     reading a null-terminated string starting at {$pointer} with no null found before end of allocation
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs
index 288798bf0c2..ef01fe88979 100644
--- a/compiler/rustc_const_eval/src/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/check_consts/check.rs
@@ -5,6 +5,7 @@ use std::borrow::Cow;
 use std::mem;
 use std::ops::Deref;
 
+use rustc_attr::{ConstStability, StabilityLevel};
 use rustc_errors::{Diag, ErrorGuaranteed};
 use rustc_hir::def_id::DefId;
 use rustc_hir::{self as hir, LangItem};
@@ -28,8 +29,8 @@ use super::ops::{self, NonConstOp, Status};
 use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop};
 use super::resolver::FlowSensitiveAnalysis;
 use super::{ConstCx, Qualif};
-use crate::const_eval::is_unstable_const_fn;
-use crate::errors::UnstableInStable;
+use crate::check_consts::is_safe_to_expose_on_stable_const_fn;
+use crate::errors;
 
 type QualifResults<'mir, 'tcx, Q> =
     rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'mir, 'mir, 'tcx, Q>>;
@@ -274,19 +275,22 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
     /// context.
     pub fn check_op_spanned<O: NonConstOp<'tcx>>(&mut self, op: O, span: Span) {
         let gate = match op.status_in_item(self.ccx) {
-            Status::Allowed => return,
-
-            Status::Unstable(gate) if self.tcx.features().enabled(gate) => {
-                let unstable_in_stable = self.ccx.is_const_stable_const_fn()
-                    && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate);
-                if unstable_in_stable {
-                    emit_unstable_in_stable_error(self.ccx, span, gate);
+            Status::Unstable { gate, safe_to_expose_on_stable, is_function_call }
+                if self.tcx.features().enabled(gate) =>
+            {
+                // Generally this is allowed since the feature gate is enabled -- except
+                // if this function wants to be safe-to-expose-on-stable.
+                if !safe_to_expose_on_stable
+                    && self.enforce_recursive_const_stability()
+                    && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate)
+                {
+                    emit_unstable_in_stable_exposed_error(self.ccx, span, gate, is_function_call);
                 }
 
                 return;
             }
 
-            Status::Unstable(gate) => Some(gate),
+            Status::Unstable { gate, .. } => Some(gate),
             Status::Forbidden => None,
         };
 
@@ -304,7 +308,13 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
                 self.error_emitted = Some(reported);
             }
 
-            ops::DiagImportance::Secondary => self.secondary_errors.push(err),
+            ops::DiagImportance::Secondary => {
+                self.secondary_errors.push(err);
+                self.tcx.dcx().span_delayed_bug(
+                    span,
+                    "compilation must fail when there is a secondary const checker error",
+                );
+            }
         }
     }
 
@@ -569,6 +579,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
 
                     ty::FnPtr(..) => {
                         self.check_op(ops::FnCallIndirect);
+                        // We can get here without an error in miri-unleashed mode... might as well
+                        // skip the rest of the checks as well then.
                         return;
                     }
                     _ => {
@@ -612,6 +624,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     // checks.
                     // FIXME(effects) we might consider moving const stability checks to typeck as well.
                     if tcx.features().effects() {
+                        // This skips the check below that ensures we only call `const fn`.
                         is_trait = true;
 
                         if let Ok(Some(instance)) =
@@ -637,6 +650,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                                 sym::const_trait_impl
                             }),
                         });
+                        // If we allowed this, we're in miri-unleashed mode, so we might
+                        // as well skip the remaining checks.
                         return;
                     }
                 }
@@ -650,28 +665,72 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                 // const-eval of the `begin_panic` fn assumes the argument is `&str`
                 if tcx.is_lang_item(callee, LangItem::BeginPanic) {
                     match args[0].node.ty(&self.ccx.body.local_decls, tcx).kind() {
-                        ty::Ref(_, ty, _) if ty.is_str() => return,
+                        ty::Ref(_, ty, _) if ty.is_str() => {}
                         _ => self.check_op(ops::PanicNonStr),
                     }
+                    // Allow this call, skip all the checks below.
+                    return;
                 }
 
                 // const-eval of `#[rustc_const_panic_str]` functions assumes the argument is `&&str`
                 if tcx.has_attr(callee, sym::rustc_const_panic_str) {
                     match args[0].node.ty(&self.ccx.body.local_decls, tcx).kind() {
                         ty::Ref(_, ty, _) if matches!(ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) =>
-                        {
-                            return;
+                            {}
+                        _ => {
+                            self.check_op(ops::PanicNonStr);
                         }
-                        _ => self.check_op(ops::PanicNonStr),
                     }
+                    // Allow this call, skip all the checks below.
+                    return;
                 }
 
                 // This can be called on stable via the `vec!` macro.
                 if tcx.is_lang_item(callee, LangItem::ExchangeMalloc) {
                     self.check_op(ops::HeapAllocation);
+                    // Allow this call, skip all the checks below.
                     return;
                 }
 
+                // Intrinsics are language primitives, not regular calls, so treat them separately.
+                if let Some(intrinsic) = tcx.intrinsic(callee) {
+                    match tcx.lookup_const_stability(callee) {
+                        None => {
+                            // Non-const intrinsic.
+                            self.check_op(ops::IntrinsicNonConst { name: intrinsic.name });
+                        }
+                        Some(ConstStability { feature: None, const_stable_indirect, .. }) => {
+                            // Intrinsic does not need a separate feature gate (we rely on the
+                            // regular stability checker). However, we have to worry about recursive
+                            // const stability.
+                            if !const_stable_indirect && self.enforce_recursive_const_stability() {
+                                self.dcx().emit_err(errors::UnmarkedIntrinsicExposed {
+                                    span: self.span,
+                                    def_path: self.tcx.def_path_str(callee),
+                                });
+                            }
+                        }
+                        Some(ConstStability {
+                            feature: Some(feature),
+                            level: StabilityLevel::Unstable { .. },
+                            const_stable_indirect,
+                            ..
+                        }) => {
+                            self.check_op(ops::IntrinsicUnstable {
+                                name: intrinsic.name,
+                                feature,
+                                const_stable_indirect,
+                            });
+                        }
+                        Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
+                            // All good.
+                        }
+                    }
+                    // This completes the checks for intrinsics.
+                    return;
+                }
+
+                // Trait functions are not `const fn` so we have to skip them here.
                 if !tcx.is_const_fn_raw(callee) && !is_trait {
                     self.check_op(ops::FnCallNonConst {
                         caller,
@@ -681,66 +740,68 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                         call_source,
                         feature: None,
                     });
+                    // If we allowed this, we're in miri-unleashed mode, so we might
+                    // as well skip the remaining checks.
                     return;
                 }
 
-                // If the `const fn` we are trying to call is not const-stable, ensure that we have
-                // the proper feature gate enabled.
-                if let Some((gate, implied_by)) = is_unstable_const_fn(tcx, callee) {
-                    trace!(?gate, "calling unstable const fn");
-                    if self.span.allows_unstable(gate) {
-                        return;
+                // Finally, stability for regular function calls -- this is the big one.
+                match tcx.lookup_const_stability(callee) {
+                    Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
+                        // All good.
                     }
-                    if let Some(implied_by_gate) = implied_by
-                        && self.span.allows_unstable(implied_by_gate)
-                    {
-                        return;
-                    }
-
-                    // Calling an unstable function *always* requires that the corresponding gate
-                    // (or implied gate) be enabled, even if the function has
-                    // `#[rustc_allow_const_fn_unstable(the_gate)]`.
-                    let gate_enabled = |gate| tcx.features().enabled(gate);
-                    let feature_gate_enabled = gate_enabled(gate);
-                    let implied_gate_enabled = implied_by.is_some_and(gate_enabled);
-                    if !feature_gate_enabled && !implied_gate_enabled {
-                        self.check_op(ops::FnCallUnstable(callee, Some(gate)));
-                        return;
-                    }
-
-                    // If this crate is not using stability attributes, or the caller is not claiming to be a
-                    // stable `const fn`, that is all that is required.
-                    if !self.ccx.is_const_stable_const_fn() {
-                        trace!("crate not using stability attributes or caller not stably const");
-                        return;
-                    }
-
-                    // Otherwise, we are something const-stable calling a const-unstable fn.
-                    if super::rustc_allow_const_fn_unstable(tcx, caller, gate) {
-                        trace!("rustc_allow_const_fn_unstable gate enabled");
-                        return;
+                    None | Some(ConstStability { feature: None, .. }) => {
+                        // This doesn't need a separate const-stability check -- const-stability equals
+                        // regular stability, and regular stability is checked separately.
+                        // However, we *do* have to worry about *recursive* const stability.
+                        if self.enforce_recursive_const_stability()
+                            && !is_safe_to_expose_on_stable_const_fn(tcx, callee)
+                        {
+                            self.dcx().emit_err(errors::UnmarkedConstFnExposed {
+                                span: self.span,
+                                def_path: self.tcx.def_path_str(callee),
+                            });
+                        }
                     }
+                    Some(ConstStability {
+                        feature: Some(feature),
+                        level: StabilityLevel::Unstable { implied_by: implied_feature, .. },
+                        ..
+                    }) => {
+                        // An unstable const fn with a feature gate.
+                        let callee_safe_to_expose_on_stable =
+                            is_safe_to_expose_on_stable_const_fn(tcx, callee);
+
+                        // We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if
+                        // the callee is safe to expose, to avoid bypassing recursive stability.
+                        if (self.span.allows_unstable(feature)
+                            || implied_feature.is_some_and(|f| self.span.allows_unstable(f)))
+                            && callee_safe_to_expose_on_stable
+                        {
+                            return;
+                        }
 
-                    self.check_op(ops::FnCallUnstable(callee, Some(gate)));
-                    return;
-                }
-
-                // FIXME(ecstaticmorse); For compatibility, we consider `unstable` callees that
-                // have no `rustc_const_stable` attributes to be const-unstable as well. This
-                // should be fixed later.
-                let callee_is_unstable_unmarked = tcx.lookup_const_stability(callee).is_none()
-                    && tcx.lookup_stability(callee).is_some_and(|s| s.is_unstable());
-                if callee_is_unstable_unmarked {
-                    trace!("callee_is_unstable_unmarked");
-                    // We do not use `const` modifiers for intrinsic "functions", as intrinsics are
-                    // `extern` functions, and these have no way to get marked `const`. So instead we
-                    // use `rustc_const_(un)stable` attributes to mean that the intrinsic is `const`
-                    if self.ccx.is_const_stable_const_fn() || tcx.intrinsic(callee).is_some() {
-                        self.check_op(ops::FnCallUnstable(callee, None));
-                        return;
+                        // We can't use `check_op` to check whether the feature is enabled because
+                        // the logic is a bit different than elsewhere: local functions don't need
+                        // the feature gate, and there might be an "implied" gate that also suffices
+                        // to allow this.
+                        let feature_enabled = callee.is_local()
+                            || tcx.features().enabled(feature)
+                            || implied_feature.is_some_and(|f| tcx.features().enabled(f));
+                        // We do *not* honor this if we are in the "danger zone": we have to enforce
+                        // recursive const-stability and the callee is not safe-to-expose. In that
+                        // case we need `check_op` to do the check.
+                        let danger_zone = !callee_safe_to_expose_on_stable
+                            && self.enforce_recursive_const_stability();
+                        if danger_zone || !feature_enabled {
+                            self.check_op(ops::FnCallUnstable {
+                                def_id: callee,
+                                feature,
+                                safe_to_expose_on_stable: callee_safe_to_expose_on_stable,
+                            });
+                        }
                     }
                 }
-                trace!("permitting call");
             }
 
             // Forbid all `Drop` terminators unless the place being dropped is a local with no
@@ -785,11 +846,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
 
             TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm),
 
-            TerminatorKind::Yield { .. } => self.check_op(ops::Coroutine(
-                self.tcx
-                    .coroutine_kind(self.body.source.def_id())
-                    .expect("Only expected to have a yield in a coroutine"),
-            )),
+            TerminatorKind::Yield { .. } => {
+                self.check_op(ops::Coroutine(
+                    self.tcx
+                        .coroutine_kind(self.body.source.def_id())
+                        .expect("Only expected to have a yield in a coroutine"),
+                ));
+            }
 
             TerminatorKind::CoroutineDrop => {
                 span_bug!(
@@ -819,8 +882,19 @@ fn is_int_bool_float_or_char(ty: Ty<'_>) -> bool {
     ty.is_bool() || ty.is_integral() || ty.is_char() || ty.is_floating_point()
 }
 
-fn emit_unstable_in_stable_error(ccx: &ConstCx<'_, '_>, span: Span, gate: Symbol) {
+fn emit_unstable_in_stable_exposed_error(
+    ccx: &ConstCx<'_, '_>,
+    span: Span,
+    gate: Symbol,
+    is_function_call: bool,
+) -> ErrorGuaranteed {
     let attr_span = ccx.tcx.def_span(ccx.def_id()).shrink_to_lo();
 
-    ccx.dcx().emit_err(UnstableInStable { gate: gate.to_string(), span, attr_span });
+    ccx.dcx().emit_err(errors::UnstableInStableExposed {
+        gate: gate.to_string(),
+        span,
+        attr_span,
+        is_function_call,
+        is_function_call2: is_function_call,
+    })
 }
diff --git a/compiler/rustc_const_eval/src/check_consts/mod.rs b/compiler/rustc_const_eval/src/check_consts/mod.rs
index 3720418d4f0..146b559f2d7 100644
--- a/compiler/rustc_const_eval/src/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/check_consts/mod.rs
@@ -59,10 +59,12 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
         self.const_kind.expect("`const_kind` must not be called on a non-const fn")
     }
 
-    pub fn is_const_stable_const_fn(&self) -> bool {
+    pub fn enforce_recursive_const_stability(&self) -> bool {
+        // We can skip this if `staged_api` is not enabled, since in such crates
+        // `lookup_const_stability` will always be `None`.
         self.const_kind == Some(hir::ConstContext::ConstFn)
             && self.tcx.features().staged_api()
-            && is_const_stable_const_fn(self.tcx, self.def_id().to_def_id())
+            && is_safe_to_expose_on_stable_const_fn(self.tcx, self.def_id().to_def_id())
     }
 
     fn is_async(&self) -> bool {
@@ -90,50 +92,38 @@ pub fn rustc_allow_const_fn_unstable(
     attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate)
 }
 
-/// Returns `true` if the given `const fn` is "const-stable".
+/// Returns `true` if the given `const fn` is "safe to expose on stable".
 ///
 /// Panics if the given `DefId` does not refer to a `const fn`.
 ///
-/// Const-stability is only relevant for `const fn` within a `staged_api` crate. Only "const-stable"
-/// functions can be called in a const-context by users of the stable compiler. "const-stable"
-/// functions are subject to more stringent restrictions than "const-unstable" functions: They
-/// cannot use unstable features and can only call other "const-stable" functions.
-pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
-    // A default body in a `#[const_trait]` is not const-stable because const
-    // trait fns currently cannot be const-stable. We shouldn't
-    // restrict default bodies to only call const-stable functions.
+/// This is relevant within a `staged_api` crate. Unlike with normal features, the use of unstable
+/// const features *recursively* taints the functions that use them. This is to avoid accidentally
+/// exposing e.g. the implementation of an unstable const intrinsic on stable. So we partition the
+/// world into two functions: those that are safe to expose on stable (and hence may not use
+/// unstable features, not even recursively), and those that are not.
+pub fn is_safe_to_expose_on_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+    // A default body in a `#[const_trait]` is not const-stable because const trait fns currently
+    // cannot be const-stable. These functions can't be called from anything stable, so we shouldn't
+    // restrict them to only call const-stable functions.
     if tcx.is_const_default_method(def_id) {
+        // FIXME(const_trait_impl): we have to eventually allow some of these if these things can ever be stable.
+        // They should probably behave like regular `const fn` for that...
         return false;
     }
 
     // Const-stability is only relevant for `const fn`.
     assert!(tcx.is_const_fn_raw(def_id));
 
-    // A function is only const-stable if it has `#[rustc_const_stable]` or it the trait it belongs
-    // to is const-stable.
     match tcx.lookup_const_stability(def_id) {
-        Some(stab) => stab.is_const_stable(),
-        None if is_parent_const_stable_trait(tcx, def_id) => {
-            // Remove this when `#![feature(const_trait_impl)]` is stabilized,
-            // returning `true` unconditionally.
-            tcx.dcx().span_delayed_bug(
-                tcx.def_span(def_id),
-                "trait implementations cannot be const stable yet",
-            );
-            true
+        None => {
+            // Only marked functions can be trusted. Note that this may be a function in a
+            // non-staged-API crate where no recursive checks were done!
+            false
+        }
+        Some(stab) => {
+            // We consider things safe-to-expose if they are stable, if they don't have any explicit
+            // const stability attribute, or if they are marked as `const_stable_indirect`.
+            stab.is_const_stable() || stab.feature.is_none() || stab.const_stable_indirect
         }
-        None => false, // By default, items are not const stable.
-    }
-}
-
-fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
-    let local_def_id = def_id.expect_local();
-    let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
-
-    let parent_owner_id = tcx.parent_hir_id(hir_id).owner;
-    if !tcx.is_const_trait_impl_raw(parent_owner_id.to_def_id()) {
-        return false;
     }
-
-    tcx.lookup_const_stability(parent_owner_id).is_some_and(|stab| stab.is_const_stable())
 }
diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs
index 5c4a899f28a..0f151dcdb03 100644
--- a/compiler/rustc_const_eval/src/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/check_consts/ops.rs
@@ -26,8 +26,16 @@ use crate::{errors, fluent_generated};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Status {
-    Allowed,
-    Unstable(Symbol),
+    Unstable {
+        /// The feature that must be enabled to use this operation.
+        gate: Symbol,
+        /// Whether it is allowed to use this operation from stable `const fn`.
+        /// This will usually be `false`.
+        safe_to_expose_on_stable: bool,
+        /// We indicate whether this is a function call, since we can use targeted
+        /// diagnostics for "callee is not safe to expose om stable".
+        is_function_call: bool,
+    },
     Forbidden,
 }
 
@@ -40,9 +48,9 @@ pub enum DiagImportance {
     Secondary,
 }
 
-/// An operation that is not *always* allowed in a const context.
+/// An operation that is *not allowed* in a const context.
 pub trait NonConstOp<'tcx>: std::fmt::Debug {
-    /// Returns an enum indicating whether this operation is allowed within the given item.
+    /// Returns an enum indicating whether this operation can be enabled with a feature gate.
     fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
         Status::Forbidden
     }
@@ -298,30 +306,78 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
 ///
 /// Contains the name of the feature that would allow the use of this function.
 #[derive(Debug)]
-pub(crate) struct FnCallUnstable(pub DefId, pub Option<Symbol>);
+pub(crate) struct FnCallUnstable {
+    pub def_id: DefId,
+    pub feature: Symbol,
+    pub safe_to_expose_on_stable: bool,
+}
 
 impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
-    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
-        let FnCallUnstable(def_id, feature) = *self;
-
-        let mut err = ccx
-            .dcx()
-            .create_err(errors::UnstableConstFn { span, def_path: ccx.tcx.def_path_str(def_id) });
+    fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+        Status::Unstable {
+            gate: self.feature,
+            safe_to_expose_on_stable: self.safe_to_expose_on_stable,
+            is_function_call: true,
+        }
+    }
 
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
+        let mut err = ccx.dcx().create_err(errors::UnstableConstFn {
+            span,
+            def_path: ccx.tcx.def_path_str(self.def_id),
+        });
         // FIXME: make this translatable
         #[allow(rustc::untranslatable_diagnostic)]
-        if ccx.is_const_stable_const_fn() {
-            err.help(fluent_generated::const_eval_const_stable);
-        } else if ccx.tcx.sess.is_nightly_build() {
-            if let Some(feature) = feature {
-                err.help(format!("add `#![feature({feature})]` to the crate attributes to enable"));
-            }
-        }
+        err.help(format!("add `#![feature({})]` to the crate attributes to enable", self.feature));
 
         err
     }
 }
 
+/// A call to an intrinsic that is just not const-callable at all.
+#[derive(Debug)]
+pub(crate) struct IntrinsicNonConst {
+    pub name: Symbol,
+}
+
+impl<'tcx> NonConstOp<'tcx> for IntrinsicNonConst {
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
+        ccx.dcx().create_err(errors::NonConstIntrinsic {
+            span,
+            name: self.name,
+            kind: ccx.const_kind(),
+        })
+    }
+}
+
+/// A call to an intrinsic that is just not const-callable at all.
+#[derive(Debug)]
+pub(crate) struct IntrinsicUnstable {
+    pub name: Symbol,
+    pub feature: Symbol,
+    pub const_stable_indirect: bool,
+}
+
+impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable {
+    fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+        Status::Unstable {
+            gate: self.feature,
+            safe_to_expose_on_stable: self.const_stable_indirect,
+            // We do *not* want to suggest to mark the intrinsic as `const_stable_indirect`,
+            // that's not a trivial change!
+            is_function_call: false,
+        }
+    }
+
+    fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
+        ccx.dcx().create_err(errors::UnstableIntrinsic {
+            span,
+            name: self.name,
+            feature: self.feature,
+        })
+    }
+}
+
 #[derive(Debug)]
 pub(crate) struct Coroutine(pub hir::CoroutineKind);
 impl<'tcx> NonConstOp<'tcx> for Coroutine {
@@ -331,7 +387,11 @@ impl<'tcx> NonConstOp<'tcx> for Coroutine {
             hir::CoroutineSource::Block,
         ) = self.0
         {
-            Status::Unstable(sym::const_async_blocks)
+            Status::Unstable {
+                gate: sym::const_async_blocks,
+                safe_to_expose_on_stable: false,
+                is_function_call: false,
+            }
         } else {
             Status::Forbidden
         }
diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
index d04d7b273f0..0173a528c22 100644
--- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
+++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
@@ -15,7 +15,7 @@ use crate::check_consts::rustc_allow_const_fn_unstable;
 /// elaboration.
 pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool {
     // Const-stable functions must always use the stable live drop checker...
-    if ccx.is_const_stable_const_fn() {
+    if ccx.enforce_recursive_const_stability() {
         // ...except if they have the feature flag set via `rustc_allow_const_fn_unstable`.
         return rustc_allow_const_fn_unstable(
             ccx.tcx,
diff --git a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
index ca0993f0580..037fdcbcf9b 100644
--- a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs
@@ -1,25 +1,8 @@
+use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
-use rustc_span::symbol::Symbol;
-use {rustc_attr as attr, rustc_hir as hir};
-
-/// Whether the `def_id` is an unstable const fn and what feature gate(s) are necessary to enable
-/// it.
-pub fn is_unstable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> Option<(Symbol, Option<Symbol>)> {
-    if tcx.is_const_fn_raw(def_id) {
-        let const_stab = tcx.lookup_const_stability(def_id)?;
-        match const_stab.level {
-            attr::StabilityLevel::Unstable { implied_by, .. } => {
-                Some((const_stab.feature, implied_by))
-            }
-            attr::StabilityLevel::Stable { .. } => None,
-        }
-    } else {
-        None
-    }
-}
 
 pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
     let parent_id = tcx.local_parent(def_id);
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 2db43a0f787..7793de9c933 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -219,7 +219,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
     }
 
     /// "Intercept" a function call, because we have something special to do for it.
-    /// All `#[rustc_do_not_const_check]` functions should be hooked here.
+    /// All `#[rustc_do_not_const_check]` functions MUST be hooked here.
     /// If this returns `Some` function, which may be `instance` or a different function with
     /// compatible arguments, then evaluation should continue with that function.
     /// If this returns `None`, the function call has been handled and the function has returned.
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index 211668cf055..38b87b72634 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -14,7 +14,7 @@ use rustc_middle::mir::interpret::{
     UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
 };
 use rustc_middle::ty::{self, Mutability, Ty};
-use rustc_span::Span;
+use rustc_span::{Span, Symbol};
 use rustc_target::abi::WrappingRange;
 use rustc_target::abi::call::AdjustForForeignAbiError;
 
@@ -44,11 +44,15 @@ pub(crate) struct MutablePtrInFinal {
 }
 
 #[derive(Diagnostic)]
-#[diag(const_eval_unstable_in_stable)]
-pub(crate) struct UnstableInStable {
+#[diag(const_eval_unstable_in_stable_exposed)]
+pub(crate) struct UnstableInStableExposed {
     pub gate: String,
     #[primary_span]
     pub span: Span,
+    #[help(const_eval_is_function_call)]
+    pub is_function_call: bool,
+    /// Need to duplicate the field so that fluent also provides it as a variable...
+    pub is_function_call2: bool,
     #[suggestion(
         const_eval_unstable_sugg,
         code = "#[rustc_const_unstable(feature = \"...\", issue = \"...\")]\n",
@@ -118,6 +122,34 @@ pub(crate) struct UnstableConstFn {
 }
 
 #[derive(Diagnostic)]
+#[diag(const_eval_unstable_intrinsic)]
+#[help]
+pub(crate) struct UnstableIntrinsic {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+    pub feature: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_unmarked_const_fn_exposed)]
+#[help]
+pub(crate) struct UnmarkedConstFnExposed {
+    #[primary_span]
+    pub span: Span,
+    pub def_path: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_unmarked_intrinsic_exposed)]
+#[help]
+pub(crate) struct UnmarkedIntrinsicExposed {
+    #[primary_span]
+    pub span: Span,
+    pub def_path: String,
+}
+
+#[derive(Diagnostic)]
 #[diag(const_eval_mutable_ref_escaping, code = E0764)]
 pub(crate) struct MutableRefEscaping {
     #[primary_span]
@@ -154,6 +186,15 @@ pub(crate) struct NonConstFnCall {
 }
 
 #[derive(Diagnostic)]
+#[diag(const_eval_non_const_intrinsic)]
+pub(crate) struct NonConstIntrinsic {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
 #[diag(const_eval_unallowed_op_in_const_context)]
 pub(crate) struct UnallowedOpInConstContext {
     #[primary_span]
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index bed500c3032..7e4bc508e5c 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -866,7 +866,9 @@ impl SyntaxExtension {
             })
             .unwrap_or_else(|| (None, helper_attrs));
         let stability = attr::find_stability(sess, attrs, span);
-        let const_stability = attr::find_const_stability(sess, attrs, span);
+        // We set `is_const_fn` false to avoid getting any implicit const stability.
+        let const_stability =
+            attr::find_const_stability(sess, attrs, span, /* is_const_fn */ false);
         let body_stability = attr::find_body_stability(sess, attrs);
         if let Some((_, sp)) = const_stability {
             sess.dcx().emit_err(errors::MacroConstStability {
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 5921fbc0fd7..0069b07ad62 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -618,11 +618,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         "allow_internal_unstable side-steps feature gating and stability checks",
     ),
     gated!(
-        rustc_allow_const_fn_unstable, Normal,
-        template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, EncodeCrossCrate::No,
-        "rustc_allow_const_fn_unstable side-steps feature gating and stability checks"
-    ),
-    gated!(
         allow_internal_unsafe, Normal, template!(Word), WarnFollowing,
         EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint",
     ),
@@ -838,6 +833,15 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         rustc_const_panic_str, Normal, template!(Word), WarnFollowing,
         EncodeCrossCrate::Yes, INTERNAL_UNSTABLE
     ),
+    rustc_attr!(
+        rustc_const_stable_indirect, Normal,
+        template!(Word), WarnFollowing, EncodeCrossCrate::No, IMPL_DETAIL,
+    ),
+    gated!(
+        rustc_allow_const_fn_unstable, Normal,
+        template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, EncodeCrossCrate::No,
+        "rustc_allow_const_fn_unstable side-steps feature gating and stability checks"
+    ),
 
     // ==========================================================================
     // Internal attributes, Layout related:
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index eab106a4403..1c6a5e9011f 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -3128,7 +3128,7 @@ impl<'tcx> TyCtxt<'tcx> {
                 Some(stability) if stability.is_const_unstable() => {
                     // has a `rustc_const_unstable` attribute, check whether the user enabled the
                     // corresponding feature gate.
-                    self.features().enabled(stability.feature)
+                    stability.feature.is_some_and(|f| self.features().enabled(f))
                 }
                 // functions without const stability are either stable user written
                 // const fn or the user is using feature gates and we thus don't
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 3f98236595b..f8ef423a9b0 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -99,6 +99,10 @@ passes_collapse_debuginfo =
 passes_confusables = attribute should be applied to an inherent method
     .label = not an inherent method
 
+passes_const_stable_not_stable =
+    attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]`
+    .label = attribute specified here
+
 passes_continue_labeled_block =
     `continue` pointing to a labeled block
     .label = labeled blocks cannot be `continue`'d
@@ -465,10 +469,10 @@ passes_may_dangle =
     `#[may_dangle]` must be applied to a lifetime or type generic parameter in `Drop` impl
 
 passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal
+
 passes_missing_const_err =
-    attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+    attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
     .help = make the function or method const
-    .label = attribute specified here
 
 passes_missing_const_stab_attr =
     {$descr} has missing const stability attribute
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 042e50d890e..b5f1eac1cba 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1574,12 +1574,20 @@ pub(crate) struct DuplicateFeatureErr {
     pub span: Span,
     pub feature: Symbol,
 }
+
 #[derive(Diagnostic)]
 #[diag(passes_missing_const_err)]
 pub(crate) struct MissingConstErr {
     #[primary_span]
     #[help]
     pub fn_sig_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(passes_const_stable_not_stable)]
+pub(crate) struct ConstStableNotStable {
+    #[primary_span]
+    pub fn_sig_span: Span,
     #[label]
     pub const_span: Span,
 }
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index a176b2bb1ad..a3165410854 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -16,7 +16,7 @@ use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId};
 use rustc_hir::hir_id::CRATE_HIR_ID;
 use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
+use rustc_hir::{Constness, FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
 use rustc_middle::middle::privacy::EffectiveVisibilities;
@@ -27,7 +27,6 @@ use rustc_session::lint;
 use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED};
 use rustc_span::Span;
 use rustc_span::symbol::{Symbol, sym};
-use rustc_target::spec::abi::Abi;
 use tracing::{debug, info};
 
 use crate::errors;
@@ -107,6 +106,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
         def_id: LocalDefId,
         item_sp: Span,
         fn_sig: Option<&'tcx hir::FnSig<'tcx>>,
+        is_foreign_item: bool,
         kind: AnnotationKind,
         inherit_deprecation: InheritDeprecation,
         inherit_const_stability: InheritConstStability,
@@ -163,30 +163,62 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
         }
 
         let stab = attr::find_stability(self.tcx.sess, attrs, item_sp);
-        let const_stab = attr::find_const_stability(self.tcx.sess, attrs, item_sp);
+        let const_stab = attr::find_const_stability(
+            self.tcx.sess,
+            attrs,
+            item_sp,
+            fn_sig.is_some_and(|s| s.header.is_const()),
+        );
         let body_stab = attr::find_body_stability(self.tcx.sess, attrs);
-        let mut const_span = None;
 
-        let const_stab = const_stab.map(|(const_stab, const_span_node)| {
-            self.index.const_stab_map.insert(def_id, const_stab);
-            const_span = Some(const_span_node);
-            const_stab
-        });
-
-        // If the current node is a function, has const stability attributes and if it doesn not have an intrinsic ABI,
-        // check if the function/method is const or the parent impl block is const
-        if let (Some(const_span), Some(fn_sig)) = (const_span, fn_sig)
-            && fn_sig.header.abi != Abi::RustIntrinsic
+        // If the current node is a function with const stability attributes (directly given or
+        // implied), check if the function/method is const or the parent impl block is const.
+        if let Some(fn_sig) = fn_sig
             && !fn_sig.header.is_const()
+            // We have to exclude foreign items as they might be intrinsics. Sadly we can't check
+            // their ABI; `fn_sig.abi` is *not* correct for foreign functions.
+            && !is_foreign_item
+            && const_stab.is_some()
             && (!self.in_trait_impl || !self.tcx.is_const_fn_raw(def_id.to_def_id()))
         {
+            self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
+        }
+
+        // If this is marked const *stable*, it must also be regular-stable.
+        if let Some((const_stab, const_span)) = const_stab
+            && let Some(fn_sig) = fn_sig
+            && const_stab.is_const_stable()
+            && !stab.is_some_and(|(s, _)| s.is_stable())
+        {
             self.tcx
                 .dcx()
-                .emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span, const_span });
+                .emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
+        }
+
+        // Stable *language* features shouldn't be used as unstable library features.
+        // (Not doing this for stable library features is checked by tidy.)
+        if let Some((
+            ConstStability { level: Unstable { .. }, feature: Some(feature), .. },
+            const_span,
+        )) = const_stab
+        {
+            if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
+                self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature {
+                    span: const_span,
+                    item_sp,
+                });
+            }
         }
 
+        let const_stab = const_stab.map(|(const_stab, _span)| {
+            self.index.const_stab_map.insert(def_id, const_stab);
+            const_stab
+        });
+
         // `impl const Trait for Type` items forward their const stability to their
         // immediate children.
+        // FIXME(effects): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
+        // Currently, once that is set, we do not inherit anything from the parent any more.
         if const_stab.is_none() {
             debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
             if let Some(parent) = self.parent_const_stab {
@@ -247,6 +279,8 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
                 }
             }
 
+            // Stable *language* features shouldn't be used as unstable library features.
+            // (Not doing this for stable library features is checked by tidy.)
             if let Stability { level: Unstable { .. }, feature } = stab {
                 if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
                     self.tcx
@@ -260,21 +294,13 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
                 self.index.implications.insert(implied_by, feature);
             }
 
-            if let Some(ConstStability { level: Unstable { .. }, feature, .. }) = const_stab {
-                if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
-                    self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature {
-                        span: const_span.unwrap(), // If const_stab contains Some(..), same is true for const_span
-                        item_sp,
-                    });
-                }
-            }
             if let Some(ConstStability {
                 level: Unstable { implied_by: Some(implied_by), .. },
                 feature,
                 ..
             }) = const_stab
             {
-                self.index.implications.insert(implied_by, feature);
+                self.index.implications.insert(implied_by, feature.unwrap());
             }
 
             self.index.stab_map.insert(def_id, stab);
@@ -372,6 +398,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
                         ctor_def_id,
                         i.span,
                         None,
+                        /* is_foreign_item */ false,
                         AnnotationKind::Required,
                         InheritDeprecation::Yes,
                         InheritConstStability::No,
@@ -390,6 +417,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             i.owner_id.def_id,
             i.span,
             fn_sig,
+            /* is_foreign_item */ false,
             kind,
             InheritDeprecation::Yes,
             const_stab_inherit,
@@ -409,6 +437,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             ti.owner_id.def_id,
             ti.span,
             fn_sig,
+            /* is_foreign_item */ false,
             AnnotationKind::Required,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -432,6 +461,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             ii.owner_id.def_id,
             ii.span,
             fn_sig,
+            /* is_foreign_item */ false,
             kind,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -447,6 +477,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             var.def_id,
             var.span,
             None,
+            /* is_foreign_item */ false,
             AnnotationKind::Required,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -457,6 +488,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
                         ctor_def_id,
                         var.span,
                         None,
+                        /* is_foreign_item */ false,
                         AnnotationKind::Required,
                         InheritDeprecation::Yes,
                         InheritConstStability::No,
@@ -475,6 +507,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             s.def_id,
             s.span,
             None,
+            /* is_foreign_item */ false,
             AnnotationKind::Required,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -486,10 +519,15 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
     }
 
     fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
+        let fn_sig = match &i.kind {
+            rustc_hir::ForeignItemKind::Fn(fn_sig, ..) => Some(fn_sig),
+            _ => None,
+        };
         self.annotate(
             i.owner_id.def_id,
             i.span,
-            None,
+            fn_sig,
+            /* is_foreign_item */ true,
             AnnotationKind::Required,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -512,6 +550,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
             p.def_id,
             p.span,
             None,
+            /* is_foreign_item */ false,
             kind,
             InheritDeprecation::No,
             InheritConstStability::No,
@@ -540,7 +579,9 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
         }
     }
 
-    fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) {
+    fn check_missing_or_wrong_const_stability(&self, def_id: LocalDefId, span: Span) {
+        // The visitor runs for "unstable-if-unmarked" crates, but we don't yet support
+        // that on the const side.
         if !self.tcx.features().staged_api() {
             return;
         }
@@ -553,11 +594,12 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
             return;
         }
 
-        let is_const = self.tcx.is_const_fn(def_id.to_def_id())
+        let is_const = self.tcx.is_const_fn_raw(def_id.to_def_id())
             || self.tcx.is_const_trait_impl_raw(def_id.to_def_id());
         let is_stable =
             self.tcx.lookup_stability(def_id).is_some_and(|stability| stability.level.is_stable());
-        let missing_const_stability_attribute = self.tcx.lookup_const_stability(def_id).is_none();
+        let missing_const_stability_attribute =
+            self.tcx.lookup_const_stability(def_id).is_none_or(|s| s.feature.is_none());
 
         if is_const && is_stable && missing_const_stability_attribute {
             let descr = self.tcx.def_descr(def_id.to_def_id());
@@ -587,7 +629,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
         }
 
         // Ensure stable `const fn` have a const stability attribute.
-        self.check_missing_const_stability(i.owner_id.def_id, i.span);
+        self.check_missing_or_wrong_const_stability(i.owner_id.def_id, i.span);
 
         intravisit::walk_item(self, i)
     }
@@ -601,7 +643,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
         let impl_def_id = self.tcx.hir().get_parent_item(ii.hir_id());
         if self.tcx.impl_trait_ref(impl_def_id).is_none() {
             self.check_missing_stability(ii.owner_id.def_id, ii.span);
-            self.check_missing_const_stability(ii.owner_id.def_id, ii.span);
+            self.check_missing_or_wrong_const_stability(ii.owner_id.def_id, ii.span);
         }
         intravisit::walk_impl_item(self, ii);
     }
@@ -670,6 +712,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
             CRATE_DEF_ID,
             tcx.hir().span(CRATE_HIR_ID),
             None,
+            /* is_foreign_item */ false,
             AnnotationKind::Required,
             InheritDeprecation::Yes,
             InheritConstStability::No,
@@ -732,12 +775,23 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
             // For implementations of traits, check the stability of each item
             // individually as it's possible to have a stable trait with unstable
             // items.
-            hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref t), self_ty, items, .. }) => {
+            hir::ItemKind::Impl(hir::Impl {
+                constness,
+                of_trait: Some(ref t),
+                self_ty,
+                items,
+                ..
+            }) => {
                 let features = self.tcx.features();
                 if features.staged_api() {
                     let attrs = self.tcx.hir().attrs(item.hir_id());
                     let stab = attr::find_stability(self.tcx.sess, attrs, item.span);
-                    let const_stab = attr::find_const_stability(self.tcx.sess, attrs, item.span);
+                    let const_stab = attr::find_const_stability(
+                        self.tcx.sess,
+                        attrs,
+                        item.span,
+                        matches!(constness, Constness::Const),
+                    );
 
                     // If this impl block has an #[unstable] attribute, give an
                     // error if all involved types and traits are stable, because
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index bf5f948fe91..134a1a1db30 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1660,6 +1660,7 @@ symbols! {
         rustc_confusables,
         rustc_const_panic_str,
         rustc_const_stable,
+        rustc_const_stable_indirect,
         rustc_const_unstable,
         rustc_conversion_suggestion,
         rustc_deallocator,
diff --git a/library/alloc/src/raw_vec.rs b/library/alloc/src/raw_vec.rs
index 45de0617f33..85a9120c7e2 100644
--- a/library/alloc/src/raw_vec.rs
+++ b/library/alloc/src/raw_vec.rs
@@ -103,7 +103,7 @@ impl<T> RawVec<T, Global> {
     /// `RawVec` with capacity `usize::MAX`. Useful for implementing
     /// delayed allocation.
     #[must_use]
-    #[rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81"))]
     pub const fn new() -> Self {
         Self::new_in(Global)
     }
@@ -179,7 +179,7 @@ impl<T, A: Allocator> RawVec<T, A> {
     /// Like `new`, but parameterized over the choice of allocator for
     /// the returned `RawVec`.
     #[inline]
-    #[rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81"))]
     pub const fn new_in(alloc: A) -> Self {
         Self { inner: RawVecInner::new_in(alloc, align_of::<T>()), _marker: PhantomData }
     }
@@ -409,7 +409,7 @@ unsafe impl<#[may_dangle] T, A: Allocator> Drop for RawVec<T, A> {
 
 impl<A: Allocator> RawVecInner<A> {
     #[inline]
-    #[rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "raw_vec_internals_const", since = "1.81"))]
     const fn new_in(alloc: A, align: usize) -> Self {
         let ptr = unsafe { core::mem::transmute(align) };
         // `cap: 0` means "unallocated". zero-sized types are ignored.
diff --git a/library/core/src/alloc/layout.rs b/library/core/src/alloc/layout.rs
index fca32b9d3c5..95cf9427e02 100644
--- a/library/core/src/alloc/layout.rs
+++ b/library/core/src/alloc/layout.rs
@@ -66,7 +66,6 @@ impl Layout {
     #[stable(feature = "alloc_layout", since = "1.28.0")]
     #[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")]
     #[inline]
-    #[rustc_allow_const_fn_unstable(ptr_alignment_type)]
     pub const fn from_size_align(size: usize, align: usize) -> Result<Self, LayoutError> {
         if Layout::is_size_align_valid(size, align) {
             // SAFETY: Layout::is_size_align_valid checks the preconditions for this call.
@@ -127,7 +126,6 @@ impl Layout {
     #[rustc_const_stable(feature = "const_alloc_layout_unchecked", since = "1.36.0")]
     #[must_use]
     #[inline]
-    #[rustc_allow_const_fn_unstable(ptr_alignment_type)]
     pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
         assert_unsafe_precondition!(
             check_library_ub,
@@ -159,7 +157,7 @@ impl Layout {
     #[must_use = "this returns the minimum alignment, \
                   without modifying the layout"]
     #[inline]
-    #[rustc_allow_const_fn_unstable(ptr_alignment_type)]
+    #[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(ptr_alignment_type))]
     pub const fn align(&self) -> usize {
         self.align.as_usize()
     }
diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs
index e1fa43296d0..f5396af2997 100644
--- a/library/core/src/cell.rs
+++ b/library/core/src/cell.rs
@@ -2287,6 +2287,7 @@ impl<T> SyncUnsafeCell<T> {
 
     /// Unwraps the value, consuming the cell.
     #[inline]
+    #[rustc_const_unstable(feature = "sync_unsafe_cell", issue = "95439")]
     pub const fn into_inner(self) -> T {
         self.value.into_inner()
     }
diff --git a/library/core/src/cell/lazy.rs b/library/core/src/cell/lazy.rs
index d323fbeac9c..5ac33516684 100644
--- a/library/core/src/cell/lazy.rs
+++ b/library/core/src/cell/lazy.rs
@@ -79,6 +79,7 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
     /// assert_eq!(LazyCell::into_inner(lazy).ok(), Some("HELLO, WORLD!".to_string()));
     /// ```
     #[unstable(feature = "lazy_cell_into_inner", issue = "125623")]
+    #[rustc_const_unstable(feature = "lazy_cell_into_inner", issue = "125623")]
     pub const fn into_inner(this: Self) -> Result<T, F> {
         match this.state.into_inner() {
             State::Init(data) => Ok(data),
diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs
index 30c0fff3104..9c667edb476 100644
--- a/library/core/src/char/methods.rs
+++ b/library/core/src/char/methods.rs
@@ -1770,7 +1770,7 @@ const fn len_utf16(code: u32) -> usize {
 /// Panics if the buffer is not large enough.
 /// A buffer of length four is large enough to encode any `char`.
 #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
-#[rustc_const_stable(feature = "const_char_encode_utf8", since = "1.83.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_char_encode_utf8", since = "1.83.0"))]
 #[doc(hidden)]
 #[inline]
 #[rustc_allow_const_fn_unstable(const_eval_select)]
diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs
index 15b00b9aa44..0f4386190ee 100644
--- a/library/core/src/ffi/c_str.rs
+++ b/library/core/src/ffi/c_str.rs
@@ -137,11 +137,11 @@ enum FromBytesWithNulErrorKind {
 
 // FIXME: const stability attributes should not be required here, I think
 impl FromBytesWithNulError {
-    #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0"))]
     const fn interior_nul(pos: usize) -> FromBytesWithNulError {
         FromBytesWithNulError { kind: FromBytesWithNulErrorKind::InteriorNul(pos) }
     }
-    #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0"))]
     const fn not_nul_terminated() -> FromBytesWithNulError {
         FromBytesWithNulError { kind: FromBytesWithNulErrorKind::NotNulTerminated }
     }
@@ -730,7 +730,7 @@ impl AsRef<CStr> for CStr {
 /// located within `isize::MAX` from `ptr`.
 #[inline]
 #[unstable(feature = "cstr_internals", issue = "none")]
-#[rustc_const_stable(feature = "const_cstr_from_ptr", since = "1.81.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_from_ptr", since = "1.81.0"))]
 #[rustc_allow_const_fn_unstable(const_eval_select)]
 const unsafe fn strlen(ptr: *const c_char) -> usize {
     const fn strlen_ct(s: *const c_char) -> usize {
diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs
index 4cbcfb07795..f3b54230bc1 100644
--- a/library/core/src/fmt/mod.rs
+++ b/library/core/src/fmt/mod.rs
@@ -333,7 +333,10 @@ pub struct Arguments<'a> {
 #[unstable(feature = "fmt_internals", issue = "none")]
 impl<'a> Arguments<'a> {
     #[inline]
-    #[rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")]
+    #[cfg_attr(
+        bootstrap,
+        rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")
+    )]
     pub const fn new_const<const N: usize>(pieces: &'a [&'static str; N]) -> Self {
         const { assert!(N <= 1) };
         Arguments { pieces, fmt: None, args: &[] }
@@ -438,6 +441,7 @@ impl<'a> Arguments<'a> {
     #[rustc_const_unstable(feature = "const_arguments_as_str", issue = "103900")]
     #[must_use]
     #[inline]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     pub const fn as_str(&self) -> Option<&'static str> {
         match (self.pieces, self.args) {
             ([], []) => Some(""),
diff --git a/library/core/src/hint.rs b/library/core/src/hint.rs
index a69f0afdb0a..78df51f2bc4 100644
--- a/library/core/src/hint.rs
+++ b/library/core/src/hint.rs
@@ -506,7 +506,7 @@ pub const fn black_box<T>(dummy: T) -> T {
 ///   # }
 ///   ```
 #[unstable(feature = "hint_must_use", issue = "94745")]
-#[rustc_const_unstable(feature = "hint_must_use", issue = "94745")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "hint_must_use", issue = "94745"))]
 #[must_use] // <-- :)
 #[inline(always)]
 pub const fn must_use<T>(value: T) -> T {
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index 97e727633c5..ff053916d96 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -14,9 +14,10 @@
 //! `#[rustc_const_unstable(feature = "const_such_and_such", issue = "01234")]` to the intrinsic declaration.
 //!
 //! If an intrinsic is supposed to be used from a `const fn` with a `rustc_const_stable` attribute,
-//! the intrinsic's attribute must be `rustc_const_stable`, too. Such a change should not be done
-//! without T-lang consultation, because it bakes a feature into the language that cannot be
-//! replicated in user code without compiler support.
+//! `#[rustc_const_stable_indirect]` needs to be added to the intrinsic (`#[rustc_const_unstable]`
+//! can be removed then). Such a change should not be done without T-lang consultation, because it
+//! may bake a feature into the language that cannot be replicated in user code without compiler
+//! support.
 //!
 //! # Volatiles
 //!
@@ -943,7 +944,11 @@ extern "rust-intrinsic" {
     /// reach code marked with this function.
     ///
     /// The stabilized version of this intrinsic is [`core::hint::unreachable_unchecked`].
-    #[rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")]
+    #[cfg_attr(
+        bootstrap,
+        rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")
+    )]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unreachable() -> !;
 }
@@ -958,7 +963,8 @@ extern "rust-intrinsic" {
 /// own, or if it does not enable any significant optimizations.
 ///
 /// The stabilized version of this intrinsic is [`core::hint::assert_unchecked`].
-#[rustc_const_stable(feature = "const_assume", since = "1.77.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_assume", since = "1.77.0"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[rustc_nounwind]
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_intrinsic]
@@ -980,7 +986,8 @@ pub const unsafe fn assume(b: bool) {
 /// any safety invariants.
 ///
 /// This intrinsic does not have a stable counterpart.
-#[rustc_const_unstable(feature = "const_likely", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_likely", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_intrinsic]
 #[rustc_nounwind]
@@ -1000,7 +1007,8 @@ pub const fn likely(b: bool) -> bool {
 /// any safety invariants.
 ///
 /// This intrinsic does not have a stable counterpart.
-#[rustc_const_unstable(feature = "const_likely", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_likely", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_intrinsic]
 #[rustc_nounwind]
@@ -1041,7 +1049,8 @@ extern "rust-intrinsic" {
     /// This will statically either panic, or do nothing.
     ///
     /// This intrinsic does not have a stable counterpart.
-    #[rustc_const_stable(feature = "const_assert_type", since = "1.59.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_assert_type", since = "1.59.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn assert_inhabited<T>();
@@ -1050,7 +1059,8 @@ extern "rust-intrinsic" {
     /// zero-initialization: This will statically either panic, or do nothing.
     ///
     /// This intrinsic does not have a stable counterpart.
-    #[rustc_const_stable(feature = "const_assert_type2", since = "1.75.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_assert_type2", since = "1.75.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn assert_zero_valid<T>();
@@ -1058,7 +1068,8 @@ extern "rust-intrinsic" {
     /// A guard for `std::mem::uninitialized`. This will statically either panic, or do nothing.
     ///
     /// This intrinsic does not have a stable counterpart.
-    #[rustc_const_stable(feature = "const_assert_type2", since = "1.75.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_assert_type2", since = "1.75.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn assert_mem_uninitialized_valid<T>();
@@ -1071,7 +1082,8 @@ extern "rust-intrinsic" {
     /// any safety invariants.
     ///
     /// Consider using [`core::panic::Location::caller`] instead.
-    #[rustc_const_stable(feature = "const_caller_location", since = "1.79.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_caller_location", since = "1.79.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn caller_location() -> &'static crate::panic::Location<'static>;
@@ -1085,7 +1097,8 @@ extern "rust-intrinsic" {
     /// it does not require an `unsafe` block.
     /// Therefore, implementations must not require the user to uphold
     /// any safety invariants.
-    #[rustc_const_stable(feature = "const_intrinsic_forget", since = "1.83.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_intrinsic_forget", since = "1.83.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn forget<T: ?Sized>(_: T);
@@ -1391,7 +1404,8 @@ extern "rust-intrinsic" {
     ///
     /// This is not expected to ever be exposed directly to users, rather it
     /// may eventually be exposed through some more-constrained API.
-    #[rustc_const_stable(feature = "const_transmute", since = "1.56.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_transmute", since = "1.56.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn transmute_unchecked<Src, Dst>(src: Src) -> Dst;
 
@@ -1408,7 +1422,8 @@ extern "rust-intrinsic" {
     /// any safety invariants.
     ///
     /// The stabilized version of this intrinsic is [`mem::needs_drop`](crate::mem::needs_drop).
-    #[rustc_const_stable(feature = "const_needs_drop", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_needs_drop", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn needs_drop<T: ?Sized>() -> bool;
@@ -1430,7 +1445,8 @@ extern "rust-intrinsic" {
     ///
     /// The stabilized version of this intrinsic is [`pointer::offset`].
     #[must_use = "returns a new pointer rather than modifying its argument"]
-    #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn offset<Ptr, Delta>(dst: Ptr, offset: Delta) -> Ptr;
 
@@ -1448,7 +1464,8 @@ extern "rust-intrinsic" {
     ///
     /// The stabilized version of this intrinsic is [`pointer::wrapping_offset`].
     #[must_use = "returns a new pointer rather than modifying its argument"]
-    #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
 
@@ -2131,7 +2148,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `count_ones` method. For example,
     /// [`u32::count_ones`]
-    #[rustc_const_stable(feature = "const_ctpop", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ctpop", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn ctpop<T: Copy>(x: T) -> u32;
@@ -2172,7 +2190,8 @@ extern "rust-intrinsic" {
     /// let num_leading = ctlz(x);
     /// assert_eq!(num_leading, 16);
     /// ```
-    #[rustc_const_stable(feature = "const_ctlz", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ctlz", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn ctlz<T: Copy>(x: T) -> u32;
@@ -2194,7 +2213,8 @@ extern "rust-intrinsic" {
     /// let num_leading = unsafe { ctlz_nonzero(x) };
     /// assert_eq!(num_leading, 3);
     /// ```
-    #[rustc_const_stable(feature = "constctlz", since = "1.50.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "constctlz", since = "1.50.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn ctlz_nonzero<T: Copy>(x: T) -> u32;
 
@@ -2234,7 +2254,8 @@ extern "rust-intrinsic" {
     /// let num_trailing = cttz(x);
     /// assert_eq!(num_trailing, 16);
     /// ```
-    #[rustc_const_stable(feature = "const_cttz", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cttz", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn cttz<T: Copy>(x: T) -> u32;
@@ -2256,7 +2277,8 @@ extern "rust-intrinsic" {
     /// let num_trailing = unsafe { cttz_nonzero(x) };
     /// assert_eq!(num_trailing, 3);
     /// ```
-    #[rustc_const_stable(feature = "const_cttz_nonzero", since = "1.53.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cttz_nonzero", since = "1.53.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn cttz_nonzero<T: Copy>(x: T) -> u32;
 
@@ -2270,7 +2292,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `swap_bytes` method. For example,
     /// [`u32::swap_bytes`]
-    #[rustc_const_stable(feature = "const_bswap", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_bswap", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn bswap<T: Copy>(x: T) -> T;
@@ -2285,7 +2308,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `reverse_bits` method. For example,
     /// [`u32::reverse_bits`]
-    #[rustc_const_stable(feature = "const_bitreverse", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_bitreverse", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn bitreverse<T: Copy>(x: T) -> T;
@@ -2311,7 +2335,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `overflowing_add` method. For example,
     /// [`u32::overflowing_add`]
-    #[rustc_const_stable(feature = "const_int_overflow", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_overflow", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn add_with_overflow<T: Copy>(x: T, y: T) -> (T, bool);
@@ -2326,7 +2351,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `overflowing_sub` method. For example,
     /// [`u32::overflowing_sub`]
-    #[rustc_const_stable(feature = "const_int_overflow", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_overflow", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn sub_with_overflow<T: Copy>(x: T, y: T) -> (T, bool);
@@ -2341,7 +2367,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `overflowing_mul` method. For example,
     /// [`u32::overflowing_mul`]
-    #[rustc_const_stable(feature = "const_int_overflow", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_overflow", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn mul_with_overflow<T: Copy>(x: T, y: T) -> (T, bool);
@@ -2360,7 +2387,11 @@ extern "rust-intrinsic" {
     /// Safe wrappers for this intrinsic are available on the integer
     /// primitives via the `checked_div` method. For example,
     /// [`u32::checked_div`]
-    #[rustc_const_stable(feature = "const_int_unchecked_div", since = "1.52.0")]
+    #[cfg_attr(
+        bootstrap,
+        rustc_const_stable(feature = "const_int_unchecked_div", since = "1.52.0")
+    )]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_div<T: Copy>(x: T, y: T) -> T;
     /// Returns the remainder of an unchecked division, resulting in
@@ -2369,7 +2400,11 @@ extern "rust-intrinsic" {
     /// Safe wrappers for this intrinsic are available on the integer
     /// primitives via the `checked_rem` method. For example,
     /// [`u32::checked_rem`]
-    #[rustc_const_stable(feature = "const_int_unchecked_rem", since = "1.52.0")]
+    #[cfg_attr(
+        bootstrap,
+        rustc_const_stable(feature = "const_int_unchecked_rem", since = "1.52.0")
+    )]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_rem<T: Copy>(x: T, y: T) -> T;
 
@@ -2379,7 +2414,8 @@ extern "rust-intrinsic" {
     /// Safe wrappers for this intrinsic are available on the integer
     /// primitives via the `checked_shl` method. For example,
     /// [`u32::checked_shl`]
-    #[rustc_const_stable(feature = "const_int_unchecked", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_unchecked", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_shl<T: Copy, U: Copy>(x: T, y: U) -> T;
     /// Performs an unchecked right shift, resulting in undefined behavior when
@@ -2388,7 +2424,8 @@ extern "rust-intrinsic" {
     /// Safe wrappers for this intrinsic are available on the integer
     /// primitives via the `checked_shr` method. For example,
     /// [`u32::checked_shr`]
-    #[rustc_const_stable(feature = "const_int_unchecked", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_unchecked", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_shr<T: Copy, U: Copy>(x: T, y: U) -> T;
 
@@ -2397,7 +2434,8 @@ extern "rust-intrinsic" {
     ///
     /// The stable counterpart of this intrinsic is `unchecked_add` on the various
     /// integer types, such as [`u16::unchecked_add`] and [`i64::unchecked_add`].
-    #[rustc_const_stable(feature = "unchecked_math", since = "1.79.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "unchecked_math", since = "1.79.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_add<T: Copy>(x: T, y: T) -> T;
 
@@ -2406,7 +2444,8 @@ extern "rust-intrinsic" {
     ///
     /// The stable counterpart of this intrinsic is `unchecked_sub` on the various
     /// integer types, such as [`u16::unchecked_sub`] and [`i64::unchecked_sub`].
-    #[rustc_const_stable(feature = "unchecked_math", since = "1.79.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "unchecked_math", since = "1.79.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_sub<T: Copy>(x: T, y: T) -> T;
 
@@ -2415,7 +2454,8 @@ extern "rust-intrinsic" {
     ///
     /// The stable counterpart of this intrinsic is `unchecked_mul` on the various
     /// integer types, such as [`u16::unchecked_mul`] and [`i64::unchecked_mul`].
-    #[rustc_const_stable(feature = "unchecked_math", since = "1.79.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "unchecked_math", since = "1.79.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn unchecked_mul<T: Copy>(x: T, y: T) -> T;
 
@@ -2429,7 +2469,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `rotate_left` method. For example,
     /// [`u32::rotate_left`]
-    #[rustc_const_stable(feature = "const_int_rotate", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_rotate", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn rotate_left<T: Copy>(x: T, shift: u32) -> T;
@@ -2444,7 +2485,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `rotate_right` method. For example,
     /// [`u32::rotate_right`]
-    #[rustc_const_stable(feature = "const_int_rotate", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_rotate", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn rotate_right<T: Copy>(x: T, shift: u32) -> T;
@@ -2459,7 +2501,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `wrapping_add` method. For example,
     /// [`u32::wrapping_add`]
-    #[rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn wrapping_add<T: Copy>(a: T, b: T) -> T;
@@ -2473,7 +2516,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `wrapping_sub` method. For example,
     /// [`u32::wrapping_sub`]
-    #[rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn wrapping_sub<T: Copy>(a: T, b: T) -> T;
@@ -2487,7 +2531,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `wrapping_mul` method. For example,
     /// [`u32::wrapping_mul`]
-    #[rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_wrapping", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn wrapping_mul<T: Copy>(a: T, b: T) -> T;
@@ -2502,7 +2547,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `saturating_add` method. For example,
     /// [`u32::saturating_add`]
-    #[rustc_const_stable(feature = "const_int_saturating", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_saturating", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn saturating_add<T: Copy>(a: T, b: T) -> T;
@@ -2516,7 +2562,8 @@ extern "rust-intrinsic" {
     /// The stabilized versions of this intrinsic are available on the integer
     /// primitives via the `saturating_sub` method. For example,
     /// [`u32::saturating_sub`]
-    #[rustc_const_stable(feature = "const_int_saturating", since = "1.40.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_saturating", since = "1.40.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn saturating_sub<T: Copy>(a: T, b: T) -> T;
@@ -2527,7 +2574,8 @@ extern "rust-intrinsic" {
     /// This intrinsic can *only* be called where the pointer is a local without
     /// projections (`read_via_copy(ptr)`, not `read_via_copy(*ptr)`) so that it
     /// trivially obeys runtime-MIR rules about derefs in operands.
-    #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_read", since = "1.71.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn read_via_copy<T>(ptr: *const T) -> T;
 
@@ -2537,7 +2585,8 @@ extern "rust-intrinsic" {
     /// This intrinsic can *only* be called where the pointer is a local without
     /// projections (`write_via_move(ptr, x)`, not `write_via_move(*ptr, x)`) so
     /// that it trivially obeys runtime-MIR rules about derefs in operands.
-    #[rustc_const_stable(feature = "const_ptr_write", since = "1.83.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_write", since = "1.83.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn write_via_move<T>(ptr: *mut T, value: T);
 
@@ -2550,7 +2599,8 @@ extern "rust-intrinsic" {
     /// any safety invariants.
     ///
     /// The stabilized version of this intrinsic is [`core::mem::discriminant`].
-    #[rustc_const_stable(feature = "const_discriminant", since = "1.75.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_discriminant", since = "1.75.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_safe_intrinsic]
     #[rustc_nounwind]
     pub fn discriminant_value<T>(v: &T) -> <T as DiscriminantKind>::Discriminant;
@@ -2584,7 +2634,8 @@ extern "rust-intrinsic" {
     pub fn nontemporal_store<T>(ptr: *mut T, val: T);
 
     /// See documentation of `<*const T>::offset_from` for details.
-    #[rustc_const_stable(feature = "const_ptr_offset_from", since = "1.65.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_offset_from", since = "1.65.0"))]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     #[rustc_nounwind]
     pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
 
@@ -2850,7 +2901,8 @@ pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
 /// assertions are enabled whenever the *user crate* has UB checks enabled. However, if the
 /// user has UB checks disabled, the checks will still get optimized out. This intrinsic is
 /// primarily used by [`ub_checks::assert_unsafe_precondition`].
-#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_ub_checks", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // just for UB checks
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[inline(always)]
 #[rustc_intrinsic]
@@ -2935,7 +2987,8 @@ pub unsafe fn vtable_align(_ptr: *const ()) -> usize {
 /// The stabilized version of this intrinsic is [`core::mem::size_of`].
 #[rustc_nounwind]
 #[unstable(feature = "core_intrinsics", issue = "none")]
-#[rustc_const_stable(feature = "const_size_of", since = "1.40.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_size_of", since = "1.40.0"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[rustc_intrinsic]
 #[rustc_intrinsic_must_be_overridden]
 pub const fn size_of<T>() -> usize {
@@ -2952,7 +3005,8 @@ pub const fn size_of<T>() -> usize {
 /// The stabilized version of this intrinsic is [`core::mem::align_of`].
 #[rustc_nounwind]
 #[unstable(feature = "core_intrinsics", issue = "none")]
-#[rustc_const_stable(feature = "const_min_align_of", since = "1.40.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_min_align_of", since = "1.40.0"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[rustc_intrinsic]
 #[rustc_intrinsic_must_be_overridden]
 pub const fn min_align_of<T>() -> usize {
@@ -3065,7 +3119,8 @@ pub const fn type_id<T: ?Sized + 'static>() -> u128 {
 /// change the possible layouts of pointers.
 #[rustc_nounwind]
 #[unstable(feature = "core_intrinsics", issue = "none")]
-#[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[rustc_intrinsic]
 #[rustc_intrinsic_must_be_overridden]
 pub const fn aggregate_raw_ptr<P: AggregateRawPtr<D, Metadata = M>, D, M>(_data: D, _meta: M) -> P {
@@ -3090,7 +3145,11 @@ impl<P: ?Sized, T: ptr::Thin> AggregateRawPtr<*mut T> for *mut P {
 /// This is used to implement functions like `ptr::metadata`.
 #[rustc_nounwind]
 #[unstable(feature = "core_intrinsics", issue = "none")]
-#[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+#[cfg_attr(
+    bootstrap,
+    cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))
+)]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
 #[rustc_intrinsic]
 #[rustc_intrinsic_must_be_overridden]
 pub const fn ptr_metadata<P: ptr::Pointee<Metadata = M> + ?Sized, M>(_ptr: *const P) -> M {
@@ -3197,7 +3256,15 @@ pub const fn ptr_metadata<P: ptr::Pointee<Metadata = M> + ?Sized, M>(_ptr: *cons
 #[rustc_diagnostic_item = "ptr_copy_nonoverlapping"]
 pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize) {
     extern "rust-intrinsic" {
-        #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.83.0")]
+        #[cfg_attr(
+            bootstrap,
+            rustc_const_stable(feature = "const_intrinsic_copy", since = "1.83.0")
+        )]
+        #[cfg_attr(
+            not(bootstrap),
+            rustc_const_unstable(feature = "core_intrinsics", issue = "none")
+        )]
+        #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
         #[rustc_nounwind]
         pub fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
     }
@@ -3301,7 +3368,15 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
 #[rustc_diagnostic_item = "ptr_copy"]
 pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
     extern "rust-intrinsic" {
-        #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.83.0")]
+        #[cfg_attr(
+            bootstrap,
+            rustc_const_stable(feature = "const_intrinsic_copy", since = "1.83.0")
+        )]
+        #[cfg_attr(
+            not(bootstrap),
+            rustc_const_unstable(feature = "core_intrinsics", issue = "none")
+        )]
+        #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
         #[rustc_nounwind]
         fn copy<T>(src: *const T, dst: *mut T, count: usize);
     }
@@ -3382,7 +3457,8 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
 #[rustc_diagnostic_item = "ptr_write_bytes"]
 pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
     extern "rust-intrinsic" {
-        #[rustc_const_stable(feature = "const_ptr_write", since = "1.83.0")]
+        #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_ptr_write", since = "1.83.0"))]
+        #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
         #[rustc_nounwind]
         fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
     }
@@ -3643,6 +3719,7 @@ pub const unsafe fn copysignf128(_x: f128, _y: f128) -> f128 {
 
 /// Inform Miri that a given pointer definitely has a certain alignment.
 #[cfg(miri)]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize) {
     extern "Rust" {
         /// Miri-provided extern function to promise that a given pointer is properly aligned for
diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs
index 86660f2e375..2cf2ea58fd4 100644
--- a/library/core/src/iter/traits/collect.rs
+++ b/library/core/src/iter/traits/collect.rs
@@ -346,7 +346,6 @@ pub trait IntoIterator {
     fn into_iter(self) -> Self::IntoIter;
 }
 
-#[rustc_const_unstable(feature = "const_intoiterator_identity", issue = "90603")]
 #[stable(feature = "rust1", since = "1.0.0")]
 impl<I: Iterator> IntoIterator for I {
     type Item = I::Item;
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 16877566765..9c3bf827438 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -107,6 +107,7 @@
 //
 // Library features:
 // tidy-alphabetical-start
+#![cfg_attr(bootstrap, feature(const_fmt_arguments_new))]
 #![feature(array_ptr_get)]
 #![feature(asm_experimental_arch)]
 #![feature(const_align_of_val)]
@@ -121,11 +122,8 @@
 #![feature(const_eval_select)]
 #![feature(const_exact_div)]
 #![feature(const_float_methods)]
-#![feature(const_fmt_arguments_new)]
 #![feature(const_hash)]
 #![feature(const_heap)]
-#![feature(const_index_range_slice_index)]
-#![feature(const_likely)]
 #![feature(const_nonnull_new)]
 #![feature(const_num_midpoint)]
 #![feature(const_option_ext)]
@@ -144,6 +142,7 @@
 #![feature(const_typed_swap)]
 #![feature(const_ub_checks)]
 #![feature(const_unicode_case_lookup)]
+#![feature(core_intrinsics)]
 #![feature(coverage_attribute)]
 #![feature(do_not_recommend)]
 #![feature(internal_impls_macro)]
@@ -159,6 +158,7 @@
 #![feature(ptr_alignment_type)]
 #![feature(ptr_metadata)]
 #![feature(set_ptr_value)]
+#![feature(slice_as_chunks)]
 #![feature(slice_ptr_get)]
 #![feature(str_internals)]
 #![feature(str_split_inclusive_remainder)]
diff --git a/library/core/src/net/ip_addr.rs b/library/core/src/net/ip_addr.rs
index d3360c18207..0d1f4a9ea3e 100644
--- a/library/core/src/net/ip_addr.rs
+++ b/library/core/src/net/ip_addr.rs
@@ -373,6 +373,7 @@ impl IpAddr {
     /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0x2, 0, 0, 0, 0, 0, 0)).is_benchmarking(), true);
     /// ```
     #[unstable(feature = "ip", issue = "27709")]
+    #[rustc_const_unstable(feature = "const_ipv4", issue = "76205")]
     #[must_use]
     #[inline]
     pub const fn is_benchmarking(&self) -> bool {
diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs
index 5ab2ab50d7c..e8161cce2fe 100644
--- a/library/core/src/num/f128.rs
+++ b/library/core/src/num/f128.rs
@@ -288,7 +288,6 @@ impl f128 {
     // concerns about portability, so this implementation is for
     // private use internally.
     #[inline]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub(crate) const fn abs_private(self) -> f128 {
         // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`.
         unsafe {
@@ -319,7 +318,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn is_infinite(self) -> bool {
         (self == f128::INFINITY) | (self == f128::NEG_INFINITY)
     }
@@ -346,7 +344,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn is_finite(self) -> bool {
         // There's no need to handle NaN separately: if self is NaN,
         // the comparison is not true, exactly as desired.
@@ -380,7 +377,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn is_subnormal(self) -> bool {
         matches!(self.classify(), FpCategory::Subnormal)
     }
@@ -412,7 +408,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn is_normal(self) -> bool {
         matches!(self.classify(), FpCategory::Normal)
     }
@@ -437,7 +432,6 @@ impl f128 {
     /// ```
     #[inline]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn classify(self) -> FpCategory {
         let bits = self.to_bits();
         match (bits & Self::MAN_MASK, bits & Self::EXP_MASK) {
@@ -915,7 +909,6 @@ impl f128 {
     /// ```
     #[inline]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_bits(self) -> u128 {
         // SAFETY: `u128` is a plain old datatype so we can always transmute to it.
@@ -964,7 +957,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn from_bits(v: u128) -> Self {
         // It turns out the safety issues with sNaN were overblown! Hooray!
         // SAFETY: `u128` is a plain old datatype so we can always transmute from it.
@@ -991,7 +983,6 @@ impl f128 {
     /// ```
     #[inline]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_be_bytes(self) -> [u8; 16] {
         self.to_bits().to_be_bytes()
@@ -1017,7 +1008,6 @@ impl f128 {
     /// ```
     #[inline]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_le_bytes(self) -> [u8; 16] {
         self.to_bits().to_le_bytes()
@@ -1054,7 +1044,6 @@ impl f128 {
     /// ```
     #[inline]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_ne_bytes(self) -> [u8; 16] {
         self.to_bits().to_ne_bytes()
@@ -1082,7 +1071,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
         Self::from_bits(u128::from_be_bytes(bytes))
     }
@@ -1109,7 +1097,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
         Self::from_bits(u128::from_le_bytes(bytes))
     }
@@ -1146,7 +1133,6 @@ impl f128 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f128", issue = "116909")]
-    #[rustc_const_unstable(feature = "f128", issue = "116909")]
     pub const fn from_ne_bytes(bytes: [u8; 16]) -> Self {
         Self::from_bits(u128::from_ne_bytes(bytes))
     }
diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs
index 60a88496696..8b3f3b7d19b 100644
--- a/library/core/src/num/f16.rs
+++ b/library/core/src/num/f16.rs
@@ -282,7 +282,6 @@ impl f16 {
     // concerns about portability, so this implementation is for
     // private use internally.
     #[inline]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub(crate) const fn abs_private(self) -> f16 {
         // SAFETY: This transmutation is fine just like in `to_bits`/`from_bits`.
         unsafe { mem::transmute::<u16, f16>(mem::transmute::<f16, u16>(self) & !Self::SIGN_MASK) }
@@ -310,7 +309,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn is_infinite(self) -> bool {
         (self == f16::INFINITY) | (self == f16::NEG_INFINITY)
     }
@@ -336,7 +334,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn is_finite(self) -> bool {
         // There's no need to handle NaN separately: if self is NaN,
         // the comparison is not true, exactly as desired.
@@ -368,7 +365,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn is_subnormal(self) -> bool {
         matches!(self.classify(), FpCategory::Subnormal)
     }
@@ -398,7 +394,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn is_normal(self) -> bool {
         matches!(self.classify(), FpCategory::Normal)
     }
@@ -422,7 +417,6 @@ impl f16 {
     /// ```
     #[inline]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn classify(self) -> FpCategory {
         let b = self.to_bits();
         match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
@@ -901,7 +895,6 @@ impl f16 {
     /// ```
     #[inline]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_bits(self) -> u16 {
         // SAFETY: `u16` is a plain old datatype so we can always transmute to it.
@@ -949,7 +942,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn from_bits(v: u16) -> Self {
         // It turns out the safety issues with sNaN were overblown! Hooray!
         // SAFETY: `u16` is a plain old datatype so we can always transmute from it.
@@ -975,7 +967,6 @@ impl f16 {
     /// ```
     #[inline]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_be_bytes(self) -> [u8; 2] {
         self.to_bits().to_be_bytes()
@@ -1000,7 +991,6 @@ impl f16 {
     /// ```
     #[inline]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_le_bytes(self) -> [u8; 2] {
         self.to_bits().to_le_bytes()
@@ -1038,7 +1028,6 @@ impl f16 {
     /// ```
     #[inline]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     #[must_use = "this returns the result of the operation, without modifying the original"]
     pub const fn to_ne_bytes(self) -> [u8; 2] {
         self.to_bits().to_ne_bytes()
@@ -1062,7 +1051,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn from_be_bytes(bytes: [u8; 2]) -> Self {
         Self::from_bits(u16::from_be_bytes(bytes))
     }
@@ -1085,7 +1073,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn from_le_bytes(bytes: [u8; 2]) -> Self {
         Self::from_bits(u16::from_le_bytes(bytes))
     }
@@ -1119,7 +1106,6 @@ impl f16 {
     #[inline]
     #[must_use]
     #[unstable(feature = "f16", issue = "116909")]
-    #[rustc_const_unstable(feature = "f16", issue = "116909")]
     pub const fn from_ne_bytes(bytes: [u8; 2]) -> Self {
         Self::from_bits(u16::from_ne_bytes(bytes))
     }
diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs
index 5e2f45884dd..05ecfe431d0 100644
--- a/library/core/src/num/mod.rs
+++ b/library/core/src/num/mod.rs
@@ -16,7 +16,7 @@ macro_rules! try_opt {
     };
 }
 
-#[allow_internal_unstable(const_likely)]
+#[cfg_attr(bootstrap, allow_internal_unstable(const_likely))]
 macro_rules! unlikely {
     ($e: expr) => {
         intrinsics::unlikely($e)
@@ -1397,7 +1397,7 @@ from_str_radix_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 }
 #[doc(hidden)]
 #[inline(always)]
 #[unstable(issue = "none", feature = "std_internals")]
-#[rustc_const_stable(feature = "const_int_from_str", since = "1.82.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_from_str", since = "1.82.0"))]
 pub const fn can_not_overflow<T>(radix: u32, is_signed_ty: bool, digits: &[u8]) -> bool {
     radix <= 16 && digits.len() <= mem::size_of::<T>() * 2 - is_signed_ty as usize
 }
@@ -1416,6 +1416,7 @@ fn from_str_radix_panic_rt(radix: u32) -> ! {
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[cold]
 #[track_caller]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 const fn from_str_radix_panic(radix: u32) {
     // The only difference between these two functions is their panic message.
     intrinsics::const_eval_select((radix,), from_str_radix_panic_ct, from_str_radix_panic_rt);
diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs
index d9036abecc5..8e62fd85912 100644
--- a/library/core/src/num/uint_macros.rs
+++ b/library/core/src/num/uint_macros.rs
@@ -3009,7 +3009,7 @@ macro_rules! uint_impl {
         // overflow cases it instead ends up returning the maximum value
         // of the type, and can return 0 for 0.
         #[inline]
-        #[rustc_const_stable(feature = "const_int_pow", since = "1.50.0")]
+        #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_int_pow", since = "1.50.0"))]
         const fn one_less_than_next_power_of_two(self) -> Self {
             if self <= 1 { return 0; }
 
diff --git a/library/core/src/panic/panic_info.rs b/library/core/src/panic/panic_info.rs
index af2c83b5460..1d950eb3625 100644
--- a/library/core/src/panic/panic_info.rs
+++ b/library/core/src/panic/panic_info.rs
@@ -168,6 +168,7 @@ impl<'a> PanicMessage<'a> {
     #[rustc_const_unstable(feature = "const_arguments_as_str", issue = "103900")]
     #[must_use]
     #[inline]
+    #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)]
     pub const fn as_str(&self) -> Option<&'static str> {
         self.message.as_str()
     }
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index 7420579e3ce..9071d6719a3 100644
--- a/library/core/src/panicking.rs
+++ b/library/core/src/panicking.rs
@@ -50,7 +50,8 @@ const _: () = assert!(cfg!(panic = "abort"), "panic_immediate_abort requires -C
 #[track_caller]
 #[lang = "panic_fmt"] // needed for const-evaluated panics
 #[rustc_do_not_const_check] // hooked by const-eval
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
     if cfg!(feature = "panic_immediate_abort") {
         super::intrinsics::abort()
@@ -84,7 +85,9 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
 // and unwinds anyway, we will hit the "unwinding out of nounwind function" guard,
 // which causes a "panic in a function that cannot unwind".
 #[rustc_nounwind]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: bool) -> ! {
     #[inline] // this should always be inlined into `panic_nounwind_fmt`
     #[track_caller]
@@ -131,7 +134,8 @@ pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: boo
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[track_caller]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 #[lang = "panic"] // used by lints and miri for panics
 pub const fn panic(expr: &'static str) -> ! {
     // Use Arguments::new_const instead of format_args!("{expr}") to potentially
@@ -169,7 +173,8 @@ macro_rules! panic_const {
                 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
                 #[cfg_attr(feature = "panic_immediate_abort", inline)]
                 #[track_caller]
-                #[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+                #[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+                #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
                 #[lang = stringify!($lang)]
                 pub const fn $lang() -> ! {
                     // Use Arguments::new_const instead of format_args!("{expr}") to potentially
@@ -216,7 +221,8 @@ panic_const! {
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[lang = "panic_nounwind"] // needed by codegen for non-unwinding panics
 #[rustc_nounwind]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn panic_nounwind(expr: &'static str) -> ! {
     panic_nounwind_fmt(fmt::Arguments::new_const(&[expr]), /* force_no_backtrace */ false);
 }
@@ -232,7 +238,8 @@ pub fn panic_nounwind_nobacktrace(expr: &'static str) -> ! {
 #[track_caller]
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn panic_explicit() -> ! {
     panic_display(&"explicit panic");
 }
@@ -249,7 +256,8 @@ pub fn unreachable_display<T: fmt::Display>(x: &T) -> ! {
 #[inline]
 #[track_caller]
 #[rustc_diagnostic_item = "panic_str_2015"]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn panic_str_2015(expr: &str) -> ! {
     panic_display(&expr);
 }
@@ -259,7 +267,8 @@ pub const fn panic_str_2015(expr: &str) -> ! {
 #[rustc_do_not_const_check] // hooked by const-eval
 // enforce a &&str argument in const-check and hook this by const-eval
 #[rustc_const_panic_str]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn panic_display<T: fmt::Display>(x: &T) -> ! {
     panic_fmt(format_args!("{}", *x));
 }
@@ -327,8 +336,9 @@ fn panic_in_cleanup() -> ! {
 }
 
 /// This function is used instead of panic_fmt in const eval.
-#[lang = "const_panic_fmt"]
-#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
+#[lang = "const_panic_fmt"] // needed by const-eval machine to replace calls to `panic_fmt` lang item
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "panic_internals", issue = "none"))]
+#[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 pub const fn const_panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
     if let Some(msg) = fmt.as_str() {
         // The panic_display function is hooked by const eval.
diff --git a/library/core/src/ptr/alignment.rs b/library/core/src/ptr/alignment.rs
index 50706fca5b0..2538d60a8ee 100644
--- a/library/core/src/ptr/alignment.rs
+++ b/library/core/src/ptr/alignment.rs
@@ -41,7 +41,7 @@ impl Alignment {
     /// This provides the same numerical value as [`mem::align_of`],
     /// but in an `Alignment` instead of a `usize`.
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn of<T>() -> Self {
         // SAFETY: rustc ensures that type alignment is always a power of two.
@@ -53,7 +53,7 @@ impl Alignment {
     ///
     /// Note that `0` is not a power of two, nor a valid alignment.
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn new(align: usize) -> Option<Self> {
         if align.is_power_of_two() {
@@ -73,7 +73,7 @@ impl Alignment {
     /// Equivalently, it must be `1 << exp` for some `exp` in `0..usize::BITS`.
     /// It must *not* be zero.
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const unsafe fn new_unchecked(align: usize) -> Self {
         assert_unsafe_precondition!(
@@ -89,7 +89,7 @@ impl Alignment {
 
     /// Returns the alignment as a [`usize`].
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn as_usize(self) -> usize {
         self.0 as usize
@@ -97,7 +97,7 @@ impl Alignment {
 
     /// Returns the alignment as a <code>[NonZero]<[usize]></code>.
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn as_nonzero(self) -> NonZero<usize> {
         // SAFETY: All the discriminants are non-zero.
@@ -118,7 +118,7 @@ impl Alignment {
     /// assert_eq!(Alignment::new(1024).unwrap().log2(), 10);
     /// ```
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn log2(self) -> u32 {
         self.as_nonzero().trailing_zeros()
@@ -148,7 +148,7 @@ impl Alignment {
     /// assert_ne!(one.mask(Alignment::of::<Align4>().mask()), one);
     /// ```
     #[unstable(feature = "ptr_alignment_type", issue = "102070")]
-    #[rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "ptr_alignment_type", issue = "102070"))]
     #[inline]
     pub const fn mask(self) -> usize {
         // SAFETY: The alignment is always nonzero, and therefore decrementing won't overflow.
diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs
index facf38894d3..75d681d76df 100644
--- a/library/core/src/ptr/const_ptr.rs
+++ b/library/core/src/ptr/const_ptr.rs
@@ -39,6 +39,7 @@ impl<T: ?Sized> *const T {
         }
 
         #[inline]
+        #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
         const fn const_impl(ptr: *const u8) -> bool {
             match (ptr).guaranteed_eq(null_mut()) {
                 Some(res) => res,
@@ -113,7 +114,7 @@ impl<T: ?Sized> *const T {
     /// println!("{:?}", unsafe { &*bad });
     /// ```
     #[unstable(feature = "set_ptr_value", issue = "75091")]
-    #[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
     #[must_use = "returns a new pointer rather than modifying its argument"]
     #[inline]
     pub const fn with_metadata_of<U>(self, meta: *const U) -> *const U
@@ -409,6 +410,7 @@ impl<T: ?Sized> *const T {
         T: Sized,
     {
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: isize, size: usize) -> bool {
@@ -761,6 +763,7 @@ impl<T: ?Sized> *const T {
     where
         T: Sized,
     {
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_ptr_ge(this: *const (), origin: *const ()) -> bool {
             fn runtime(this: *const (), origin: *const ()) -> bool {
                 this >= origin
@@ -902,6 +905,7 @@ impl<T: ?Sized> *const T {
     {
         #[cfg(debug_assertions)]
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: usize, size: usize) -> bool {
@@ -1010,6 +1014,7 @@ impl<T: ?Sized> *const T {
     {
         #[cfg(debug_assertions)]
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: usize, size: usize) -> bool {
@@ -1622,6 +1627,7 @@ impl<T: ?Sized> *const T {
         }
 
         #[inline]
+        #[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
         const fn const_impl(ptr: *const (), align: usize) -> bool {
             // We can't use the address of `self` in a `const fn`, so we use `align_offset` instead.
             ptr.align_offset(align) == 0
diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs
index 09c4002dbc7..5f20cb2ee72 100644
--- a/library/core/src/ptr/metadata.rs
+++ b/library/core/src/ptr/metadata.rs
@@ -92,7 +92,7 @@ pub trait Thin = Pointee<Metadata = ()>;
 ///
 /// assert_eq!(std::ptr::metadata("foo"), 3_usize);
 /// ```
-#[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
 #[inline]
 pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata {
     ptr_metadata(ptr)
@@ -106,7 +106,7 @@ pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata {
 ///
 /// [`slice::from_raw_parts`]: crate::slice::from_raw_parts
 #[unstable(feature = "ptr_metadata", issue = "81513")]
-#[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
 #[inline]
 pub const fn from_raw_parts<T: ?Sized>(
     data_pointer: *const impl Thin,
@@ -120,7 +120,7 @@ pub const fn from_raw_parts<T: ?Sized>(
 ///
 /// See the documentation of [`from_raw_parts`] for more details.
 #[unstable(feature = "ptr_metadata", issue = "81513")]
-#[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
 #[inline]
 pub const fn from_raw_parts_mut<T: ?Sized>(
     data_pointer: *mut impl Thin,
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index 03cab232742..ee04cafb8a1 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -591,8 +591,8 @@ pub const fn null_mut<T: ?Sized + Thin>() -> *mut T {
 /// This is a [Strict Provenance][crate::ptr#strict-provenance] API.
 #[inline(always)]
 #[must_use]
-#[rustc_const_stable(feature = "stable_things_using_strict_provenance", since = "1.61.0")]
 #[stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
+#[rustc_const_stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
 pub const fn without_provenance<T>(addr: usize) -> *const T {
     // An int-to-pointer transmute currently has exactly the intended semantics: it creates a
     // pointer without provenance. Note that this is *not* a stable guarantee about transmute
@@ -613,8 +613,8 @@ pub const fn without_provenance<T>(addr: usize) -> *const T {
 /// some other means.
 #[inline(always)]
 #[must_use]
-#[rustc_const_stable(feature = "stable_things_using_strict_provenance", since = "1.61.0")]
 #[stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
+#[rustc_const_stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
 pub const fn dangling<T>() -> *const T {
     without_provenance(mem::align_of::<T>())
 }
@@ -634,8 +634,8 @@ pub const fn dangling<T>() -> *const T {
 /// This is a [Strict Provenance][crate::ptr#strict-provenance] API.
 #[inline(always)]
 #[must_use]
-#[rustc_const_stable(feature = "stable_things_using_strict_provenance", since = "1.61.0")]
 #[stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
+#[rustc_const_stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
 pub const fn without_provenance_mut<T>(addr: usize) -> *mut T {
     // An int-to-pointer transmute currently has exactly the intended semantics: it creates a
     // pointer without provenance. Note that this is *not* a stable guarantee about transmute
@@ -656,8 +656,8 @@ pub const fn without_provenance_mut<T>(addr: usize) -> *mut T {
 /// some other means.
 #[inline(always)]
 #[must_use]
-#[rustc_const_stable(feature = "stable_things_using_strict_provenance", since = "1.61.0")]
 #[stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
+#[rustc_const_stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
 pub const fn dangling_mut<T>() -> *mut T {
     without_provenance_mut(mem::align_of::<T>())
 }
@@ -1854,6 +1854,7 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
 /// Any questions go to @nagisa.
 #[allow(ptr_to_integer_transmute_in_consts)]
 #[lang = "align_offset"]
+#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
 pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
     // FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
     // 1, where the method versions of these operations are not inlined.
diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs
index 031939cf0d5..408e722267a 100644
--- a/library/core/src/ptr/mut_ptr.rs
+++ b/library/core/src/ptr/mut_ptr.rs
@@ -94,7 +94,7 @@ impl<T: ?Sized> *mut T {
     /// // This dereference is UB. The pointer only has provenance for `x` but points to `y`.
     /// println!("{:?}", unsafe { &*bad });
     #[unstable(feature = "set_ptr_value", issue = "75091")]
-    #[rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "ptr_metadata_const", since = "1.83.0"))]
     #[must_use = "returns a new pointer rather than modifying its argument"]
     #[inline]
     pub const fn with_metadata_of<U>(self, meta: *const U) -> *mut U
@@ -405,6 +405,7 @@ impl<T: ?Sized> *mut T {
         T: Sized,
     {
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: isize, size: usize) -> bool {
@@ -984,6 +985,7 @@ impl<T: ?Sized> *mut T {
     {
         #[cfg(debug_assertions)]
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: usize, size: usize) -> bool {
@@ -1092,6 +1094,7 @@ impl<T: ?Sized> *mut T {
     {
         #[cfg(debug_assertions)]
         #[inline]
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
             #[inline]
             fn runtime(this: *const (), count: usize, size: usize) -> bool {
@@ -1871,6 +1874,7 @@ impl<T: ?Sized> *mut T {
         }
 
         #[inline]
+        #[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
         const fn const_impl(ptr: *mut (), align: usize) -> bool {
             // We can't use the address of `self` in a `const fn`, so we use `align_offset` instead.
             ptr.align_offset(align) == 0
diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs
index d80e1e700aa..86ef1f3f005 100644
--- a/library/core/src/ptr/non_null.rs
+++ b/library/core/src/ptr/non_null.rs
@@ -1508,7 +1508,6 @@ impl<T> NonNull<[T]> {
     #[inline]
     #[must_use]
     #[unstable(feature = "slice_ptr_get", issue = "74265")]
-    #[rustc_const_unstable(feature = "slice_ptr_get", issue = "74265")]
     pub const fn as_non_null_ptr(self) -> NonNull<T> {
         self.cast()
     }
diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs
index 4810ebe01f9..a796820a7e4 100644
--- a/library/core/src/ptr/unique.rs
+++ b/library/core/src/ptr/unique.rs
@@ -92,6 +92,7 @@ impl<T: ?Sized> Unique<T> {
 
     /// Creates a new `Unique` if `ptr` is non-null.
     #[inline]
+    #[rustc_const_unstable(feature = "ptr_internals", issue = "none")]
     pub const fn new(ptr: *mut T) -> Option<Self> {
         if let Some(pointer) = NonNull::new(ptr) {
             Some(Unique { pointer, _marker: PhantomData })
diff --git a/library/core/src/slice/ascii.rs b/library/core/src/slice/ascii.rs
index a03e9fbae11..21e0460072f 100644
--- a/library/core/src/slice/ascii.rs
+++ b/library/core/src/slice/ascii.rs
@@ -346,6 +346,8 @@ pub const fn is_ascii_simple(mut bytes: &[u8]) -> bool {
 /// If any of these loads produces something for which `contains_nonascii`
 /// (above) returns true, then we know the answer is false.
 #[inline]
+#[rustc_allow_const_fn_unstable(const_raw_ptr_comparison, const_pointer_is_aligned)] // only in a debug assertion
+#[rustc_allow_const_fn_unstable(const_align_offset)] // behavior does not change when `align_offset` fails
 const fn is_ascii(s: &[u8]) -> bool {
     const USIZE_SIZE: usize = mem::size_of::<usize>();
 
diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs
index bc8571c8503..231ab7396ad 100644
--- a/library/core/src/slice/index.rs
+++ b/library/core/src/slice/index.rs
@@ -31,6 +31,7 @@ where
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[track_caller]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 const fn slice_start_index_len_fail(index: usize, len: usize) -> ! {
     // FIXME(const-hack): once integer formatting in panics is possible, we
     // should use the same implementation at compiletime and runtime.
@@ -52,6 +53,7 @@ const fn slice_start_index_len_fail_ct(_: usize, _: usize) -> ! {
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[track_caller]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 const fn slice_end_index_len_fail(index: usize, len: usize) -> ! {
     // FIXME(const-hack): once integer formatting in panics is possible, we
     // should use the same implementation at compiletime and runtime.
@@ -73,6 +75,7 @@ const fn slice_end_index_len_fail_ct(_: usize, _: usize) -> ! {
 #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
 #[cfg_attr(feature = "panic_immediate_abort", inline)]
 #[track_caller]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 const fn slice_index_order_fail(index: usize, end: usize) -> ! {
     // FIXME(const-hack): once integer formatting in panics is possible, we
     // should use the same implementation at compiletime and runtime.
@@ -310,7 +313,6 @@ unsafe impl<T> SliceIndex<[T]> for usize {
 
 /// Because `IndexRange` guarantees `start <= end`, fewer checks are needed here
 /// than there are for a general `Range<usize>` (which might be `100..3`).
-#[rustc_const_unstable(feature = "const_index_range_slice_index", issue = "none")]
 unsafe impl<T> SliceIndex<[T]> for ops::IndexRange {
     type Output = [T];
 
diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs
index be19c3d3bc1..57604623262 100644
--- a/library/core/src/slice/memchr.rs
+++ b/library/core/src/slice/memchr.rs
@@ -15,7 +15,7 @@ const USIZE_BYTES: usize = mem::size_of::<usize>();
 /// bytes where the borrow propagated all the way to the most significant
 /// bit."
 #[inline]
-#[rustc_const_stable(feature = "const_memchr", since = "1.65.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_memchr", since = "1.65.0"))]
 const fn contains_zero_byte(x: usize) -> bool {
     x.wrapping_sub(LO_USIZE) & !x & HI_USIZE != 0
 }
@@ -23,7 +23,7 @@ const fn contains_zero_byte(x: usize) -> bool {
 /// Returns the first index matching the byte `x` in `text`.
 #[inline]
 #[must_use]
-#[rustc_const_stable(feature = "const_memchr", since = "1.65.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_memchr", since = "1.65.0"))]
 pub const fn memchr(x: u8, text: &[u8]) -> Option<usize> {
     // Fast path for small slices.
     if text.len() < 2 * USIZE_BYTES {
@@ -34,7 +34,7 @@ pub const fn memchr(x: u8, text: &[u8]) -> Option<usize> {
 }
 
 #[inline]
-#[rustc_const_stable(feature = "const_memchr", since = "1.65.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_memchr", since = "1.65.0"))]
 const fn memchr_naive(x: u8, text: &[u8]) -> Option<usize> {
     let mut i = 0;
 
@@ -52,7 +52,7 @@ const fn memchr_naive(x: u8, text: &[u8]) -> Option<usize> {
 
 #[rustc_allow_const_fn_unstable(const_cmp)]
 #[rustc_allow_const_fn_unstable(const_align_offset)]
-#[rustc_const_stable(feature = "const_memchr", since = "1.65.0")]
+#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_memchr", since = "1.65.0"))]
 const fn memchr_aligned(x: u8, text: &[u8]) -> Option<usize> {
     // Scan for a single byte value by reading two `usize` words at a time.
     //
diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs
index dbcfe946440..27e51afa800 100644
--- a/library/core/src/slice/mod.rs
+++ b/library/core/src/slice/mod.rs
@@ -1265,6 +1265,7 @@ impl<T> [T] {
     /// // let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // Zero-length chunks are never allowed
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[must_use]
     pub const unsafe fn as_chunks_unchecked<const N: usize>(&self) -> &[[T; N]] {
@@ -1310,6 +1311,7 @@ impl<T> [T] {
     /// assert_eq!(chunks, &[['R', 'u'], ['s', 't']]);
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[track_caller]
     #[must_use]
@@ -1344,6 +1346,7 @@ impl<T> [T] {
     /// assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[track_caller]
     #[must_use]
@@ -1422,6 +1425,7 @@ impl<T> [T] {
     /// // let chunks: &[[_; 0]] = slice.as_chunks_unchecked_mut() // Zero-length chunks are never allowed
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[must_use]
     pub const unsafe fn as_chunks_unchecked_mut<const N: usize>(&mut self) -> &mut [[T; N]] {
@@ -1462,6 +1466,7 @@ impl<T> [T] {
     /// assert_eq!(v, &[1, 1, 2, 2, 9]);
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[track_caller]
     #[must_use]
@@ -1502,6 +1507,7 @@ impl<T> [T] {
     /// assert_eq!(v, &[9, 1, 1, 2, 2]);
     /// ```
     #[unstable(feature = "slice_as_chunks", issue = "74985")]
+    #[rustc_const_unstable(feature = "slice_as_chunks", issue = "74985")]
     #[inline]
     #[track_caller]
     #[must_use]
diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
index 17ba18c2a66..93b4ad5c1c9 100644
--- a/library/core/src/sync/atomic.rs
+++ b/library/core/src/sync/atomic.rs
@@ -2122,7 +2122,8 @@ macro_rules! atomic_int {
      $stable_access:meta,
      $stable_from:meta,
      $stable_nand:meta,
-     $const_stable:meta,
+     $const_stable_new:meta,
+     $const_stable_into_inner:meta,
      $diagnostic_item:meta,
      $s_int_type:literal,
      $extra_feature:expr,
@@ -2204,7 +2205,7 @@ macro_rules! atomic_int {
             /// ```
             #[inline]
             #[$stable]
-            #[$const_stable]
+            #[$const_stable_new]
             #[must_use]
             pub const fn new(v: $int_type) -> Self {
                 Self {v: UnsafeCell::new(v)}
@@ -2406,7 +2407,7 @@ macro_rules! atomic_int {
             /// ```
             #[inline]
             #[$stable_access]
-            #[rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0")]
+            #[$const_stable_into_inner]
             pub const fn into_inner(self) -> $int_type {
                 self.v.into_inner()
             }
@@ -3054,6 +3055,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicI8"),
     "i8",
     "",
@@ -3072,6 +3074,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicU8"),
     "u8",
     "",
@@ -3090,6 +3093,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicI16"),
     "i16",
     "",
@@ -3108,6 +3112,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicU16"),
     "u16",
     "",
@@ -3126,6 +3131,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicI32"),
     "i32",
     "",
@@ -3144,6 +3150,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicU32"),
     "u32",
     "",
@@ -3162,6 +3169,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicI64"),
     "i64",
     "",
@@ -3180,6 +3188,7 @@ atomic_int! {
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     stable(feature = "integer_atomics_stable", since = "1.34.0"),
     rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicU64"),
     "u64",
     "",
@@ -3197,7 +3206,8 @@ atomic_int! {
     unstable(feature = "integer_atomics", issue = "99069"),
     unstable(feature = "integer_atomics", issue = "99069"),
     unstable(feature = "integer_atomics", issue = "99069"),
-    rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
+    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"),
     "i128",
     "#![feature(integer_atomics)]\n\n",
@@ -3215,7 +3225,8 @@ atomic_int! {
     unstable(feature = "integer_atomics", issue = "99069"),
     unstable(feature = "integer_atomics", issue = "99069"),
     unstable(feature = "integer_atomics", issue = "99069"),
-    rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
+    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
+    rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
     cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"),
     "u128",
     "#![feature(integer_atomics)]\n\n",
@@ -3238,6 +3249,7 @@ macro_rules! atomic_int_ptr_sized {
             stable(feature = "atomic_from", since = "1.23.0"),
             stable(feature = "atomic_nand", since = "1.27.0"),
             rustc_const_stable(feature = "const_ptr_sized_atomics", since = "1.24.0"),
+            rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
             cfg_attr(not(test), rustc_diagnostic_item = "AtomicIsize"),
             "isize",
             "",
@@ -3256,6 +3268,7 @@ macro_rules! atomic_int_ptr_sized {
             stable(feature = "atomic_from", since = "1.23.0"),
             stable(feature = "atomic_nand", since = "1.27.0"),
             rustc_const_stable(feature = "const_ptr_sized_atomics", since = "1.24.0"),
+            rustc_const_stable(feature = "const_atomic_into_inner", since = "1.79.0"),
             cfg_attr(not(test), rustc_diagnostic_item = "AtomicUsize"),
             "usize",
             "",
diff --git a/library/core/src/sync/exclusive.rs b/library/core/src/sync/exclusive.rs
index fbf8dafad18..af25f139739 100644
--- a/library/core/src/sync/exclusive.rs
+++ b/library/core/src/sync/exclusive.rs
@@ -106,6 +106,7 @@ impl<T: Sized> Exclusive<T> {
 
     /// Unwrap the value contained in the `Exclusive`
     #[unstable(feature = "exclusive_wrapper", issue = "98407")]
+    #[rustc_const_unstable(feature = "exclusive_wrapper", issue = "98407")]
     #[must_use]
     #[inline]
     pub const fn into_inner(self) -> T {
@@ -129,6 +130,7 @@ impl<T: ?Sized> Exclusive<T> {
     /// access to the underlying value, but _pinned_ `Exclusive`s only
     /// produce _pinned_ access to the underlying value.
     #[unstable(feature = "exclusive_wrapper", issue = "98407")]
+    #[rustc_const_unstable(feature = "exclusive_wrapper", issue = "98407")]
     #[must_use]
     #[inline]
     pub const fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
@@ -152,6 +154,7 @@ impl<T: ?Sized> Exclusive<T> {
     /// a _pinned mutable_ reference to a `T`. This allows you to skip
     /// building an `Exclusive` with [`Exclusive::new`].
     #[unstable(feature = "exclusive_wrapper", issue = "98407")]
+    #[rustc_const_unstable(feature = "exclusive_wrapper", issue = "98407")]
     #[must_use]
     #[inline]
     pub const fn from_pin_mut(r: Pin<&'_ mut T>) -> Pin<&'_ mut Exclusive<T>> {
diff --git a/library/core/src/task/wake.rs b/library/core/src/task/wake.rs
index 3e795e7b5e3..fb7af8234dd 100644
--- a/library/core/src/task/wake.rs
+++ b/library/core/src/task/wake.rs
@@ -321,7 +321,7 @@ impl<'a> ContextBuilder<'a> {
     /// Creates a ContextBuilder from a Waker.
     #[inline]
     #[unstable(feature = "local_waker", issue = "118959")]
-    #[rustc_const_stable(feature = "const_waker", since = "1.82.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_waker", since = "1.82.0"))]
     pub const fn from_waker(waker: &'a Waker) -> Self {
         // SAFETY: LocalWaker is just Waker without thread safety
         let local_waker = unsafe { transmute(waker) };
@@ -379,7 +379,7 @@ impl<'a> ContextBuilder<'a> {
     /// Builds the `Context`.
     #[inline]
     #[unstable(feature = "local_waker", issue = "118959")]
-    #[rustc_const_stable(feature = "const_waker", since = "1.82.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_waker", since = "1.82.0"))]
     pub const fn build(self) -> Context<'a> {
         let ContextBuilder { waker, local_waker, ext, _marker, _marker2 } = self;
         Context { waker, local_waker, ext: AssertUnwindSafe(ext), _marker, _marker2 }
diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs
index daaaf5a7195..91566439ade 100644
--- a/library/core/src/ub_checks.rs
+++ b/library/core/src/ub_checks.rs
@@ -47,7 +47,7 @@ use crate::intrinsics::{self, const_eval_select};
 /// order to call it. Since the precompiled standard library is built with full debuginfo and these
 /// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
 /// debuginfo to have a measurable compile-time impact on debug builds.
-#[allow_internal_unstable(const_ub_checks)] // permit this to be called in stably-const fn
+#[cfg_attr(bootstrap, allow_internal_unstable(const_ub_checks))] // permit this to be called in stably-const fn
 #[macro_export]
 #[unstable(feature = "ub_checks", issue = "none")]
 macro_rules! assert_unsafe_precondition {
@@ -64,7 +64,8 @@ macro_rules! assert_unsafe_precondition {
             #[rustc_no_mir_inline]
             #[inline]
             #[rustc_nounwind]
-            #[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
+            #[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_ub_checks", issue = "none"))]
+            #[rustc_allow_const_fn_unstable(const_ptr_is_null, const_ub_checks)] // only for UB checks
             const fn precondition_check($($name:$ty),*) {
                 if !$e {
                     ::core::panicking::panic_nounwind(
@@ -90,8 +91,9 @@ pub use intrinsics::ub_checks as check_library_ub;
 ///
 /// The intention is to not do that when running in the interpreter, as that one has its own
 /// language UB checks which generally produce better errors.
-#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
+#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_ub_checks", issue = "none"))]
 #[inline]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
 pub(crate) const fn check_language_ub() -> bool {
     #[inline]
     fn runtime() -> bool {
@@ -116,6 +118,7 @@ pub(crate) const fn check_language_ub() -> bool {
 /// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
 /// check is anyway not executed in `const`.
 #[inline]
+#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
 pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize, is_zst: bool) -> bool {
     ptr.is_aligned_to(align) && (is_zst || !ptr.is_null())
 }
@@ -132,6 +135,7 @@ pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
 /// Note that in const-eval this function just returns `true` and therefore must
 /// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
 #[inline]
+#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
 pub(crate) const fn is_nonoverlapping(
     src: *const (),
     dst: *const (),
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index 14603aa30e8..5719d29659a 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -1,4 +1,5 @@
 // tidy-alphabetical-start
+#![cfg_attr(bootstrap, feature(const_likely))]
 #![cfg_attr(bootstrap, feature(strict_provenance))]
 #![cfg_attr(not(bootstrap), feature(strict_provenance_lints))]
 #![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
@@ -22,7 +23,6 @@
 #![feature(const_eval_select)]
 #![feature(const_hash)]
 #![feature(const_heap)]
-#![feature(const_likely)]
 #![feature(const_nonnull_new)]
 #![feature(const_num_midpoint)]
 #![feature(const_option_ext)]
diff --git a/library/std/src/sys/sync/once/queue.rs b/library/std/src/sys/sync/once/queue.rs
index 3e83a4a088f..177d0d7744a 100644
--- a/library/std/src/sys/sync/once/queue.rs
+++ b/library/std/src/sys/sync/once/queue.rs
@@ -116,7 +116,7 @@ fn to_state(current: StateAndQueue) -> usize {
 
 impl Once {
     #[inline]
-    #[rustc_const_stable(feature = "const_once_new", since = "1.32.0")]
+    #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_once_new", since = "1.32.0"))]
     pub const fn new() -> Once {
         Once { state_and_queue: AtomicPtr::new(ptr::without_provenance_mut(INCOMPLETE)) }
     }
diff --git a/library/std/src/sys/thread_local/key/racy.rs b/library/std/src/sys/thread_local/key/racy.rs
index 69f11458c32..97df8997b80 100644
--- a/library/std/src/sys/thread_local/key/racy.rs
+++ b/library/std/src/sys/thread_local/key/racy.rs
@@ -30,7 +30,7 @@ const KEY_SENTVAL: usize = 0;
 const KEY_SENTVAL: usize = libc::PTHREAD_KEYS_MAX + 1;
 
 impl LazyKey {
-    #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "thread_local_internals", issue = "none"))]
     pub const fn new(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> LazyKey {
         LazyKey { key: atomic::AtomicUsize::new(KEY_SENTVAL), dtor }
     }
diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs
index f5a2aaa6c6a..58f291ffdb9 100644
--- a/library/std/src/sys/thread_local/os.rs
+++ b/library/std/src/sys/thread_local/os.rs
@@ -60,7 +60,7 @@ struct Value<T: 'static> {
 }
 
 impl<T: 'static> Storage<T> {
-    #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "thread_local_internals", issue = "none"))]
     pub const fn new() -> Storage<T> {
         Storage { key: LazyKey::new(Some(destroy_value::<T>)), marker: PhantomData }
     }
diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs
index 88bf186700f..9edb3fa4193 100644
--- a/library/std/src/thread/local.rs
+++ b/library/std/src/thread/local.rs
@@ -237,7 +237,7 @@ impl<T: 'static> LocalKey<T> {
         reason = "recently added to create a key",
         issue = "none"
     )]
-    #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
+    #[cfg_attr(bootstrap, rustc_const_unstable(feature = "thread_local_internals", issue = "none"))]
     pub const unsafe fn new(inner: fn(Option<&mut Option<T>>) -> *const T) -> LocalKey<T> {
         LocalKey { inner }
     }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index c8cb9267eb2..1c060fa0150 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -8,7 +8,6 @@ use arrayvec::ArrayVec;
 use rustc_ast::MetaItemInner;
 use rustc_ast_pretty::pprust;
 use rustc_attr::{ConstStability, Deprecation, Stability, StableSince};
-use rustc_const_eval::const_eval::is_unstable_const_fn;
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId};
@@ -641,12 +640,11 @@ impl Item {
             asyncness: ty::Asyncness,
         ) -> hir::FnHeader {
             let sig = tcx.fn_sig(def_id).skip_binder();
-            let constness =
-                if tcx.is_const_fn(def_id) || is_unstable_const_fn(tcx, def_id).is_some() {
-                    hir::Constness::Const
-                } else {
-                    hir::Constness::NotConst
-                };
+            let constness = if tcx.is_const_fn_raw(def_id) {
+                hir::Constness::Const
+            } else {
+                hir::Constness::NotConst
+            };
             let asyncness = match asyncness {
                 ty::Asyncness::Yes => hir::IsAsync::Async(DUMMY_SP),
                 ty::Asyncness::No => hir::IsAsync::NotAsync,
@@ -664,9 +662,7 @@ impl Item {
                         safety
                     },
                     abi,
-                    constness: if tcx.is_const_fn(def_id)
-                        || is_unstable_const_fn(tcx, def_id).is_some()
-                    {
+                    constness: if tcx.is_const_fn_raw(def_id) {
                         hir::Constness::Const
                     } else {
                         hir::Constness::NotConst
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index ce96d1d0a95..8446235fb18 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1010,7 +1010,9 @@ fn render_stability_since_raw_with_extra(
                 // don't display const unstable if entirely unstable
                 None
             } else {
-                let unstable = if let Some(n) = issue {
+                let unstable = if let Some(n) = issue
+                    && let Some(feature) = feature
+                {
                     format!(
                         "<a \
                         href=\"https://github.com/rust-lang/rust/issues/{n}\" \
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index cb9b3985bf6..7c9dcd41e6a 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -37,7 +37,6 @@ extern crate rustc_abi;
 extern crate rustc_ast;
 extern crate rustc_ast_pretty;
 extern crate rustc_attr;
-extern crate rustc_const_eval;
 extern crate rustc_data_structures;
 extern crate rustc_driver;
 extern crate rustc_errors;
diff --git a/tests/rustdoc/const-display.rs b/tests/rustdoc/const-display.rs
index a71825d883d..bc4270c421d 100644
--- a/tests/rustdoc/const-display.rs
+++ b/tests/rustdoc/const-display.rs
@@ -89,10 +89,4 @@ impl Bar {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[rustc_const_stable(feature = "const2", since = "1.2.0")]
     pub const fn stable_impl() -> u32 { 42 }
-
-    // Show const-stability even for unstable functions.
-    //@ matches 'foo/struct.Bar.html' '//span[@class="since"]' '^const: 1.3.0$'
-    #[unstable(feature = "foo2", issue = "none")]
-    #[rustc_const_stable(feature = "const3", since = "1.3.0")]
-    pub const fn const_stable_unstable() -> u32 { 42 }
 }
diff --git a/tests/ui/borrowck/issue-64453.rs b/tests/ui/borrowck/issue-64453.rs
index 33d55be5812..5f1f35d6ca9 100644
--- a/tests/ui/borrowck/issue-64453.rs
+++ b/tests/ui/borrowck/issue-64453.rs
@@ -3,7 +3,6 @@ struct Value;
 
 static settings_dir: String = format!("");
 //~^ ERROR cannot call non-const fn
-//~| ERROR is not yet stable as a const
 
 fn from_string(_: String) -> Value {
     Value
diff --git a/tests/ui/borrowck/issue-64453.stderr b/tests/ui/borrowck/issue-64453.stderr
index e671817633b..98b05ead649 100644
--- a/tests/ui/borrowck/issue-64453.stderr
+++ b/tests/ui/borrowck/issue-64453.stderr
@@ -1,12 +1,3 @@
-error: `Arguments::<'a>::new_const` is not yet stable as a const fn
-  --> $DIR/issue-64453.rs:4:31
-   |
-LL | static settings_dir: String = format!("");
-   |                               ^^^^^^^^^^^
-   |
-   = help: add `#![feature(const_fmt_arguments_new)]` to the crate attributes to enable
-   = note: this error originates in the macro `$crate::__export::format_args` which comes from the expansion of the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
-
 error[E0015]: cannot call non-const fn `format` in statics
   --> $DIR/issue-64453.rs:4:31
    |
@@ -18,7 +9,7 @@ LL | static settings_dir: String = format!("");
    = note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0507]: cannot move out of static item `settings_dir`
-  --> $DIR/issue-64453.rs:14:37
+  --> $DIR/issue-64453.rs:13:37
    |
 LL |     let settings_data = from_string(settings_dir);
    |                                     ^^^^^^^^^^^^ move occurs because `settings_dir` has type `String`, which does not implement the `Copy` trait
@@ -28,7 +19,7 @@ help: consider cloning the value if the performance cost is acceptable
 LL |     let settings_data = from_string(settings_dir.clone());
    |                                                 ++++++++
 
-error: aborting due to 3 previous errors
+error: aborting due to 2 previous errors
 
 Some errors have detailed explanations: E0015, E0507.
 For more information about an error, try `rustc --explain E0015`.
diff --git a/tests/ui/consts/auxiliary/unstable_but_const_stable.rs b/tests/ui/consts/auxiliary/unstable_but_const_stable.rs
deleted file mode 100644
index 88044b0272c..00000000000
--- a/tests/ui/consts/auxiliary/unstable_but_const_stable.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-#![feature(staged_api, rustc_attrs, intrinsics)]
-#![stable(since="1.0.0", feature = "stable")]
-
-extern "rust-intrinsic" {
-    #[unstable(feature = "unstable", issue = "42")]
-    #[rustc_const_stable(feature = "stable", since = "1.0.0")]
-    #[rustc_nounwind]
-    pub fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
-}
-
-#[unstable(feature = "unstable", issue = "42")]
-#[rustc_const_stable(feature = "stable", since = "1.0.0")]
-pub const fn some_unstable_fn() {}
diff --git a/tests/ui/consts/auxiliary/unstable_intrinsic.rs b/tests/ui/consts/auxiliary/unstable_intrinsic.rs
new file mode 100644
index 00000000000..edef499dbb1
--- /dev/null
+++ b/tests/ui/consts/auxiliary/unstable_intrinsic.rs
@@ -0,0 +1,26 @@
+#![feature(staged_api, rustc_attrs, intrinsics)]
+#![stable(since="1.0.0", feature = "stable")]
+
+#[stable(since="1.0.0", feature = "stable")]
+pub mod old_way {
+    extern "rust-intrinsic" {
+        #[unstable(feature = "unstable", issue = "42")]
+        pub fn size_of_val<T>(x: *const T) -> usize;
+
+        #[unstable(feature = "unstable", issue = "42")]
+        #[rustc_const_unstable(feature = "unstable", issue = "42")]
+        pub fn min_align_of_val<T>(x: *const T) -> usize;
+    }
+}
+
+#[stable(since="1.0.0", feature = "stable")]
+pub mod new_way {
+    #[unstable(feature = "unstable", issue = "42")]
+    #[rustc_intrinsic]
+    pub const unsafe fn size_of_val<T>(x: *const T) -> usize { 42 }
+
+    #[unstable(feature = "unstable", issue = "42")]
+    #[rustc_const_unstable(feature = "unstable", issue = "42")]
+    #[rustc_intrinsic]
+    pub const unsafe fn min_align_of_val<T>(x: *const T) -> usize { 42 }
+}
diff --git a/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.rs b/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.rs
index 4b3cf70739c..6c93c0e63b6 100644
--- a/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.rs
+++ b/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.rs
@@ -3,7 +3,7 @@
             we're apparently really bad at it",
             issue = "none")]
 
-#![feature(staged_api)]
+#![feature(staged_api, foo)]
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_unstable(feature="foo", issue = "none")]
@@ -11,7 +11,7 @@ const fn foo() -> u32 { 42 }
 
 fn meh() -> u32 { 42 }
 
-const fn bar() -> u32 { foo() } //~ ERROR `foo` is not yet stable as a const fn
+const fn bar() -> u32 { foo() } //~ ERROR cannot use `#[feature(foo)]`
 
 fn a() {
     let _: &'static u32 = &foo(); //~ ERROR temporary value dropped while borrowed
diff --git a/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.stderr b/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.stderr
index 2e697b219c5..1de1c78faf6 100644
--- a/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.stderr
+++ b/tests/ui/consts/const-eval/dont_promote_unstable_const_fn.stderr
@@ -1,10 +1,20 @@
-error: `foo` is not yet stable as a const fn
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo)]`
   --> $DIR/dont_promote_unstable_const_fn.rs:14:25
    |
 LL | const fn bar() -> u32 { foo() }
    |                         ^^^^^
    |
-   = help: add `#![feature(foo)]` to the crate attributes to enable
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn bar() -> u32 { foo() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo)]
+LL | const fn bar() -> u32 { foo() }
+   |
 
 error[E0716]: temporary value dropped while borrowed
   --> $DIR/dont_promote_unstable_const_fn.rs:17:28
diff --git a/tests/ui/consts/const-eval/simd/insert_extract.rs b/tests/ui/consts/const-eval/simd/insert_extract.rs
index f4f25327aaf..57d4b4888ca 100644
--- a/tests/ui/consts/const-eval/simd/insert_extract.rs
+++ b/tests/ui/consts/const-eval/simd/insert_extract.rs
@@ -11,8 +11,11 @@
 #[repr(simd)] struct f32x4([f32; 4]);
 
 extern "rust-intrinsic" {
+    #[stable(feature = "foo", since = "1.3.37")]
     #[rustc_const_stable(feature = "foo", since = "1.3.37")]
     fn simd_insert<T, U>(x: T, idx: u32, val: U) -> T;
+
+    #[stable(feature = "foo", since = "1.3.37")]
     #[rustc_const_stable(feature = "foo", since = "1.3.37")]
     fn simd_extract<T, U>(x: T, idx: u32) -> U;
 }
diff --git a/tests/ui/consts/const-unstable-intrinsic.rs b/tests/ui/consts/const-unstable-intrinsic.rs
new file mode 100644
index 00000000000..050abc6dd46
--- /dev/null
+++ b/tests/ui/consts/const-unstable-intrinsic.rs
@@ -0,0 +1,76 @@
+//! Ensure that unstable intrinsics can actually not be called,
+//! neither within a crate nor cross-crate.
+//@ aux-build:unstable_intrinsic.rs
+#![feature(staged_api, rustc_attrs, intrinsics)]
+#![stable(since="1.0.0", feature = "stable")]
+#![feature(local)]
+
+extern crate unstable_intrinsic;
+
+fn main() {
+    const_main();
+}
+
+const fn const_main() {
+    let x = 42;
+    unsafe {
+        unstable_intrinsic::old_way::size_of_val(&x);
+        //~^ERROR: unstable library feature 'unstable'
+        //~|ERROR: cannot call non-const intrinsic
+        unstable_intrinsic::old_way::min_align_of_val(&x);
+        //~^ERROR: unstable library feature 'unstable'
+        //~|ERROR: not yet stable as a const intrinsic
+        unstable_intrinsic::new_way::size_of_val(&x);
+        //~^ERROR: unstable library feature 'unstable'
+        //~|ERROR: cannot be (indirectly) exposed to stable
+        unstable_intrinsic::new_way::min_align_of_val(&x);
+        //~^ERROR: unstable library feature 'unstable'
+        //~|ERROR: not yet stable as a const intrinsic
+
+        old_way::size_of_val(&x);
+        //~^ERROR: cannot call non-const intrinsic
+        old_way::min_align_of_val(&x);
+        //~^ERROR: cannot use `#[feature(local)]`
+        new_way::size_of_val(&x);
+        //~^ERROR: cannot be (indirectly) exposed to stable
+        new_way::min_align_of_val(&x);
+        //~^ERROR: cannot use `#[feature(local)]`
+    }
+}
+
+#[stable(since="1.0.0", feature = "stable")]
+pub mod old_way {
+    extern "rust-intrinsic" {
+        #[unstable(feature = "local", issue = "42")]
+        pub fn size_of_val<T>(x: *const T) -> usize;
+
+        #[unstable(feature = "local", issue = "42")]
+        #[rustc_const_unstable(feature = "local", issue = "42")]
+        pub fn min_align_of_val<T>(x: *const T) -> usize;
+    }
+}
+
+#[stable(since="1.0.0", feature = "stable")]
+pub mod new_way {
+    #[unstable(feature = "local", issue = "42")]
+    #[rustc_intrinsic]
+    pub const unsafe fn size_of_val<T>(x: *const T) -> usize { 42 }
+
+    #[unstable(feature = "local", issue = "42")]
+    #[rustc_const_unstable(feature = "local", issue = "42")]
+    #[rustc_intrinsic]
+    pub const unsafe fn min_align_of_val<T>(x: *const T) -> usize { 42 }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")]
+#[inline]
+pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
+    // Const stability attributes are not inherited from parent items.
+    extern "rust-intrinsic" {
+        fn copy<T>(src: *const T, dst: *mut T, count: usize);
+    }
+
+    unsafe { copy(src, dst, count) }
+    //~^ ERROR cannot call non-const intrinsic
+}
diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr
new file mode 100644
index 00000000000..33a434c503d
--- /dev/null
+++ b/tests/ui/consts/const-unstable-intrinsic.stderr
@@ -0,0 +1,127 @@
+error[E0658]: use of unstable library feature 'unstable'
+  --> $DIR/const-unstable-intrinsic.rs:17:9
+   |
+LL |         unstable_intrinsic::old_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error[E0658]: use of unstable library feature 'unstable'
+  --> $DIR/const-unstable-intrinsic.rs:20:9
+   |
+LL |         unstable_intrinsic::old_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error[E0658]: use of unstable library feature 'unstable'
+  --> $DIR/const-unstable-intrinsic.rs:23:9
+   |
+LL |         unstable_intrinsic::new_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error[E0658]: use of unstable library feature 'unstable'
+  --> $DIR/const-unstable-intrinsic.rs:26:9
+   |
+LL |         unstable_intrinsic::new_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: cannot call non-const intrinsic `size_of_val` in constant functions
+  --> $DIR/const-unstable-intrinsic.rs:17:9
+   |
+LL |         unstable_intrinsic::old_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `min_align_of_val` is not yet stable as a const intrinsic
+  --> $DIR/const-unstable-intrinsic.rs:20:9
+   |
+LL |         unstable_intrinsic::old_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+
+error: intrinsic `unstable_intrinsic::new_way::size_of_val` cannot be (indirectly) exposed to stable
+  --> $DIR/const-unstable-intrinsic.rs:23:9
+   |
+LL |         unstable_intrinsic::new_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval)
+
+error: `min_align_of_val` is not yet stable as a const intrinsic
+  --> $DIR/const-unstable-intrinsic.rs:26:9
+   |
+LL |         unstable_intrinsic::new_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+
+error: cannot call non-const intrinsic `size_of_val` in constant functions
+  --> $DIR/const-unstable-intrinsic.rs:30:9
+   |
+LL |         old_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
+  --> $DIR/const-unstable-intrinsic.rs:32:9
+   |
+LL |         old_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn const_main() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local)]
+LL | const fn const_main() {
+   |
+
+error: intrinsic `new_way::size_of_val` cannot be (indirectly) exposed to stable
+  --> $DIR/const-unstable-intrinsic.rs:34:9
+   |
+LL |         new_way::size_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval)
+
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
+  --> $DIR/const-unstable-intrinsic.rs:36:9
+   |
+LL |         new_way::min_align_of_val(&x);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn const_main() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local)]
+LL | const fn const_main() {
+   |
+
+error: cannot call non-const intrinsic `copy` in constant functions
+  --> $DIR/const-unstable-intrinsic.rs:74:14
+   |
+LL |     unsafe { copy(src, dst, count) }
+   |              ^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 13 previous errors
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/consts/copy-intrinsic.rs b/tests/ui/consts/copy-intrinsic.rs
index 805c03da546..62917c0b98b 100644
--- a/tests/ui/consts/copy-intrinsic.rs
+++ b/tests/ui/consts/copy-intrinsic.rs
@@ -5,9 +5,11 @@
 use std::mem;
 
 extern "rust-intrinsic" {
+    #[stable(feature = "dummy", since = "1.0.0")]
     #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")]
     fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
 
+    #[stable(feature = "dummy", since = "1.0.0")]
     #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")]
     fn copy<T>(src: *const T, dst: *mut T, count: usize);
 }
diff --git a/tests/ui/consts/copy-intrinsic.stderr b/tests/ui/consts/copy-intrinsic.stderr
index da8139129c9..29a88f6270b 100644
--- a/tests/ui/consts/copy-intrinsic.stderr
+++ b/tests/ui/consts/copy-intrinsic.stderr
@@ -1,23 +1,23 @@
 error[E0080]: evaluation of constant value failed
-  --> $DIR/copy-intrinsic.rs:28:5
+  --> $DIR/copy-intrinsic.rs:30:5
    |
 LL |     copy_nonoverlapping(0x100 as *const i32, dangle, 1);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 4 bytes of memory, but got 0x100[noalloc] which is a dangling pointer (it has no provenance)
 
 error[E0080]: evaluation of constant value failed
-  --> $DIR/copy-intrinsic.rs:37:5
+  --> $DIR/copy-intrinsic.rs:39:5
    |
 LL |     copy_nonoverlapping(dangle, 0x100 as *mut i32, 1);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 4 bytes of memory, but got ALLOC0+0x28 which is at or beyond the end of the allocation of size 4 bytes
 
 error[E0080]: evaluation of constant value failed
-  --> $DIR/copy-intrinsic.rs:44:5
+  --> $DIR/copy-intrinsic.rs:46:5
    |
 LL |     copy(&x, &mut y, 1usize << (mem::size_of::<usize>() * 8 - 1));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `copy`
 
 error[E0080]: evaluation of constant value failed
-  --> $DIR/copy-intrinsic.rs:50:5
+  --> $DIR/copy-intrinsic.rs:52:5
    |
 LL |     copy_nonoverlapping(&x, &mut y, 1usize << (mem::size_of::<usize>() * 8 - 1));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflow computing total size of `copy_nonoverlapping`
diff --git a/tests/ui/consts/intrinsic_without_const_stab.rs b/tests/ui/consts/intrinsic_without_const_stab.rs
deleted file mode 100644
index 40ec65d51be..00000000000
--- a/tests/ui/consts/intrinsic_without_const_stab.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![feature(intrinsics, staged_api)]
-#![stable(feature = "core", since = "1.6.0")]
-
-#[stable(feature = "rust1", since = "1.0.0")]
-#[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")]
-#[inline]
-pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
-    // Const stability attributes are not inherited from parent items.
-    extern "rust-intrinsic" {
-        fn copy<T>(src: *const T, dst: *mut T, count: usize);
-    }
-
-    unsafe { copy(src, dst, count) }
-    //~^ ERROR cannot call non-const fn
-}
-
-fn main() {}
diff --git a/tests/ui/consts/intrinsic_without_const_stab.stderr b/tests/ui/consts/intrinsic_without_const_stab.stderr
deleted file mode 100644
index e3143080c5f..00000000000
--- a/tests/ui/consts/intrinsic_without_const_stab.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0015]: cannot call non-const fn `copy::copy::<T>` in constant functions
-  --> $DIR/intrinsic_without_const_stab.rs:13:14
-   |
-LL |     unsafe { copy(src, dst, count) }
-   |              ^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0015`.
diff --git a/tests/ui/consts/intrinsic_without_const_stab_fail.rs b/tests/ui/consts/intrinsic_without_const_stab_fail.rs
deleted file mode 100644
index 2b0745b3c11..00000000000
--- a/tests/ui/consts/intrinsic_without_const_stab_fail.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-#![feature(intrinsics, staged_api)]
-#![stable(feature = "core", since = "1.6.0")]
-
-extern "rust-intrinsic" {
-    fn copy<T>(src: *const T, dst: *mut T, count: usize);
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-#[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")]
-#[inline]
-pub const unsafe fn stuff<T>(src: *const T, dst: *mut T, count: usize) {
-    unsafe { copy(src, dst, count) } //~ ERROR cannot call non-const fn
-}
-
-fn main() {}
diff --git a/tests/ui/consts/intrinsic_without_const_stab_fail.stderr b/tests/ui/consts/intrinsic_without_const_stab_fail.stderr
deleted file mode 100644
index 8ade68eb2a9..00000000000
--- a/tests/ui/consts/intrinsic_without_const_stab_fail.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0015]: cannot call non-const fn `copy::<T>` in constant functions
-  --> $DIR/intrinsic_without_const_stab_fail.rs:12:14
-   |
-LL |     unsafe { copy(src, dst, count) }
-   |              ^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0015`.
diff --git a/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.rs b/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.rs
index 461499e942f..d6f07994e82 100644
--- a/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.rs
+++ b/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.rs
@@ -5,7 +5,7 @@
             issue = "none")]
 
 #![feature(foo, foo2)]
-#![feature(const_async_blocks, staged_api)]
+#![feature(const_async_blocks, staged_api, rustc_attrs)]
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_unstable(feature="foo", issue = "none")]
@@ -14,33 +14,55 @@ const fn foo() -> u32 { 42 }
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const fn bar() -> u32 { foo() } //~ ERROR not yet stable as a const fn
+const fn bar() -> u32 { foo() } //~ ERROR cannot use `#[feature(foo)]`
 
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature = "foo2", issue = "none")]
 const fn foo2() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const fn bar2() -> u32 { foo2() } //~ ERROR not yet stable as a const fn
+const fn bar2() -> u32 { foo2() } //~ ERROR cannot use `#[feature(foo2)]`
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // conformity is required
 const fn bar3() -> u32 {
     let x = async { 13 };
-    //~^ ERROR const-stable function cannot use `#[feature(const_async_blocks)]`
+    //~^ ERROR cannot use `#[feature(const_async_blocks)]`
     foo()
-    //~^ ERROR is not yet stable as a const fn
+    //~^ ERROR cannot use `#[feature(foo)]`
 }
 
 // check whether this function cannot be called even with the feature gate active
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature = "foo2", issue = "none")]
 const fn foo2_gated() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const fn bar2_gated() -> u32 { foo2_gated() } //~ ERROR not yet stable as a const fn
+const fn bar2_gated() -> u32 { foo2_gated() } //~ ERROR cannot use `#[feature(foo2)]`
+
+// Functions without any attribute are checked like stable functions,
+// even if they are in a stable module.
+mod stable {
+    #![stable(feature = "rust1", since = "1.0.0")]
+
+    pub(crate) const fn bar2_gated_stable_indirect() -> u32 { super::foo2_gated() } //~ ERROR cannot use `#[feature(foo2)]`
+}
+// And same for const-unstable functions that are marked as "stable_indirect".
+#[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature="foo", issue = "none")]
+#[rustc_const_stable_indirect]
+const fn stable_indirect() -> u32 { foo2_gated() } //~ ERROR cannot use `#[feature(foo2)]`
+
+// These functiuons *can* be called from fully stable functions.
+#[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_stable(feature = "rust1", since = "1.0.0")]
+const fn bar2_gated_exposed() -> u32 {
+    stable::bar2_gated_stable_indirect() + stable_indirect()
+}
 
 fn main() {}
diff --git a/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.stderr b/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.stderr
index fedc5a4809d..899cec07ac7 100644
--- a/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.stderr
+++ b/tests/ui/consts/min_const_fn/min_const_fn_libstd_stability.stderr
@@ -1,51 +1,127 @@
-error: `foo` is not yet stable as a const fn
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo)]`
   --> $DIR/min_const_fn_libstd_stability.rs:17:25
    |
 LL | const fn bar() -> u32 { foo() }
    |                         ^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn bar() -> u32 { foo() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo)]
+LL | const fn bar() -> u32 { foo() }
+   |
 
-error: `foo2` is not yet stable as a const fn
-  --> $DIR/min_const_fn_libstd_stability.rs:25:26
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:26:26
    |
 LL | const fn bar2() -> u32 { foo2() }
    |                          ^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn bar2() -> u32 { foo2() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const fn bar2() -> u32 { foo2() }
+   |
 
-error: const-stable function cannot use `#[feature(const_async_blocks)]`
-  --> $DIR/min_const_fn_libstd_stability.rs:31:13
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(const_async_blocks)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:32:13
    |
 LL |     let x = async { 13 };
    |             ^^^^^^^^^^^^
    |
-help: if the function is not (yet) meant to be stable, make this function unstably const
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
    |
 LL + #[rustc_const_unstable(feature = "...", issue = "...")]
 LL | const fn bar3() -> u32 {
    |
-help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval)
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
    |
 LL + #[rustc_allow_const_fn_unstable(const_async_blocks)]
 LL | const fn bar3() -> u32 {
    |
 
-error: `foo` is not yet stable as a const fn
-  --> $DIR/min_const_fn_libstd_stability.rs:33:5
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:34:5
    |
 LL |     foo()
    |     ^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn bar3() -> u32 {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo)]
+LL | const fn bar3() -> u32 {
+   |
 
-error: `foo2_gated` is not yet stable as a const fn
-  --> $DIR/min_const_fn_libstd_stability.rs:44:32
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:46:32
    |
 LL | const fn bar2_gated() -> u32 { foo2_gated() }
    |                                ^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn bar2_gated() -> u32 { foo2_gated() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const fn bar2_gated() -> u32 { foo2_gated() }
+   |
+
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:53:63
+   |
+LL |     pub(crate) const fn bar2_gated_stable_indirect() -> u32 { super::foo2_gated() }
+   |                                                               ^^^^^^^^^^^^^^^^^^^
+   |
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL +     #[rustc_const_unstable(feature = "...", issue = "...")]
+LL |     pub(crate) const fn bar2_gated_stable_indirect() -> u32 { super::foo2_gated() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL +     #[rustc_allow_const_fn_unstable(foo2)]
+LL |     pub(crate) const fn bar2_gated_stable_indirect() -> u32 { super::foo2_gated() }
+   |
+
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_fn_libstd_stability.rs:59:37
+   |
+LL | const fn stable_indirect() -> u32 { foo2_gated() }
+   |                                     ^^^^^^^^^^^^
+   |
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn stable_indirect() -> u32 { foo2_gated() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const fn stable_indirect() -> u32 { foo2_gated() }
+   |
 
-error: aborting due to 5 previous errors
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.rs b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.rs
index 274b4444799..3e82b9ff924 100644
--- a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.rs
+++ b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.rs
@@ -13,24 +13,26 @@ const unsafe fn foo() -> u32 { 42 }
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const unsafe fn bar() -> u32 { unsafe { foo() } } //~ ERROR not yet stable as a const fn
+const unsafe fn bar() -> u32 { unsafe { foo() } } //~ ERROR cannot use `#[feature(foo)]`
 
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature = "foo2", issue = "none")]
 const unsafe fn foo2() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const unsafe fn bar2() -> u32 { unsafe { foo2() } } //~ ERROR not yet stable as a const fn
+const unsafe fn bar2() -> u32 { unsafe { foo2() } } //~ ERROR cannot use `#[feature(foo2)]`
 
 // check whether this function cannot be called even with the feature gate active
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature = "foo2", issue = "none")]
 const unsafe fn foo2_gated() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
 const unsafe fn bar2_gated() -> u32 { unsafe { foo2_gated() } }
-//~^ ERROR not yet stable as a const fn
+//~^ ERROR cannot use `#[feature(foo2)]`
 
 fn main() {}
diff --git a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.stderr b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.stderr
index 353b117efbc..442a079020f 100644
--- a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.stderr
+++ b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability.stderr
@@ -1,26 +1,56 @@
-error: `foo` is not yet stable as a const fn
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo)]`
   --> $DIR/min_const_unsafe_fn_libstd_stability.rs:16:41
    |
 LL | const unsafe fn bar() -> u32 { unsafe { foo() } }
    |                                         ^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar() -> u32 { unsafe { foo() } }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo)]
+LL | const unsafe fn bar() -> u32 { unsafe { foo() } }
+   |
 
-error: `foo2` is not yet stable as a const fn
-  --> $DIR/min_const_unsafe_fn_libstd_stability.rs:24:42
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_unsafe_fn_libstd_stability.rs:25:42
    |
 LL | const unsafe fn bar2() -> u32 { unsafe { foo2() } }
    |                                          ^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar2() -> u32 { unsafe { foo2() } }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const unsafe fn bar2() -> u32 { unsafe { foo2() } }
+   |
 
-error: `foo2_gated` is not yet stable as a const fn
-  --> $DIR/min_const_unsafe_fn_libstd_stability.rs:33:48
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_unsafe_fn_libstd_stability.rs:35:48
    |
 LL | const unsafe fn bar2_gated() -> u32 { unsafe { foo2_gated() } }
    |                                                ^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar2_gated() -> u32 { unsafe { foo2_gated() } }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const unsafe fn bar2_gated() -> u32 { unsafe { foo2_gated() } }
+   |
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.rs b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.rs
index 94b62071362..cc7eaa51a6f 100644
--- a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.rs
+++ b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.rs
@@ -13,23 +13,25 @@ const fn foo() -> u32 { 42 }
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const unsafe fn bar() -> u32 { foo() } //~ ERROR not yet stable as a const fn
+const unsafe fn bar() -> u32 { foo() } //~ ERROR cannot use `#[feature(foo)]`
 
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature="foo2", issue = "none")]
 const fn foo2() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const unsafe fn bar2() -> u32 { foo2() } //~ ERROR not yet stable as a const fn
+const unsafe fn bar2() -> u32 { foo2() } //~ ERROR cannot use `#[feature(foo2)]`
 
 // check whether this function cannot be called even with the feature gate active
 #[unstable(feature = "foo2", issue = "none")]
+#[rustc_const_unstable(feature="foo2", issue = "none")]
 const fn foo2_gated() -> u32 { 42 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "rust1", since = "1.0.0")]
 // can't call non-min_const_fn
-const unsafe fn bar2_gated() -> u32 { foo2_gated() } //~ ERROR not yet stable as a const fn
+const unsafe fn bar2_gated() -> u32 { foo2_gated() } //~ ERROR cannot use `#[feature(foo2)]`
 
 fn main() {}
diff --git a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.stderr b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.stderr
index e90ba9b912f..ff37cba7b9a 100644
--- a/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.stderr
+++ b/tests/ui/consts/min_const_fn/min_const_unsafe_fn_libstd_stability2.stderr
@@ -1,26 +1,56 @@
-error: `foo` is not yet stable as a const fn
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo)]`
   --> $DIR/min_const_unsafe_fn_libstd_stability2.rs:16:32
    |
 LL | const unsafe fn bar() -> u32 { foo() }
    |                                ^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar() -> u32 { foo() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo)]
+LL | const unsafe fn bar() -> u32 { foo() }
+   |
 
-error: `foo2` is not yet stable as a const fn
-  --> $DIR/min_const_unsafe_fn_libstd_stability2.rs:24:33
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_unsafe_fn_libstd_stability2.rs:25:33
    |
 LL | const unsafe fn bar2() -> u32 { foo2() }
    |                                 ^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar2() -> u32 { foo2() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const unsafe fn bar2() -> u32 { foo2() }
+   |
 
-error: `foo2_gated` is not yet stable as a const fn
-  --> $DIR/min_const_unsafe_fn_libstd_stability2.rs:33:39
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(foo2)]`
+  --> $DIR/min_const_unsafe_fn_libstd_stability2.rs:35:39
    |
 LL | const unsafe fn bar2_gated() -> u32 { foo2_gated() }
    |                                       ^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const unsafe fn bar2_gated() -> u32 { foo2_gated() }
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(foo2)]
+LL | const unsafe fn bar2_gated() -> u32 { foo2_gated() }
+   |
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/consts/rustc-const-stability-require-const.rs b/tests/ui/consts/rustc-const-stability-require-const.rs
index 4fb259b335c..6cc3f0f0da1 100644
--- a/tests/ui/consts/rustc-const-stability-require-const.rs
+++ b/tests/ui/consts/rustc-const-stability-require-const.rs
@@ -1,16 +1,16 @@
 #![crate_type = "lib"]
-#![feature(staged_api)]
+#![feature(staged_api, rustc_attrs)]
 #![stable(feature = "foo", since = "1.0.0")]
 
 #[stable(feature = "foo", since = "1.0.0")]
 #[rustc_const_unstable(feature = "const_foo", issue = "none")]
 pub fn foo() {}
-//~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+//~^ ERROR require the function or method to be `const`
 
 #[stable(feature = "bar", since = "1.0.0")]
 #[rustc_const_stable(feature = "const_bar", since = "1.0.0")]
 pub fn bar() {}
-//~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+//~^ ERROR require the function or method to be `const`
 
 #[stable(feature = "potato", since = "1.0.0")]
 pub struct Potato;
@@ -19,23 +19,23 @@ impl Potato {
     #[stable(feature = "salad", since = "1.0.0")]
     #[rustc_const_unstable(feature = "const_salad", issue = "none")]
     pub fn salad(&self) -> &'static str { "mmmmmm" }
-    //~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+    //~^ ERROR require the function or method to be `const`
 
     #[stable(feature = "roasted", since = "1.0.0")]
     #[rustc_const_unstable(feature = "const_roasted", issue = "none")]
     pub fn roasted(&self) -> &'static str { "mmmmmmmmmm" }
-    //~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+    //~^ ERROR require the function or method to be `const`
 }
 
 #[stable(feature = "bar", since = "1.0.0")]
 #[rustc_const_stable(feature = "const_bar", since = "1.0.0")]
 pub extern "C" fn bar_c() {}
-//~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+//~^ ERROR require the function or method to be `const`
 
 #[stable(feature = "foo", since = "1.0.0")]
 #[rustc_const_unstable(feature = "const_foo", issue = "none")]
 pub extern "C" fn foo_c() {}
-//~^ ERROR attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+//~^ ERROR require the function or method to be `const`
 
 
 #[stable(feature = "foobar", since = "1.0.0")]
@@ -45,3 +45,20 @@ pub const fn foobar() {}
 #[stable(feature = "barfoo", since = "1.0.0")]
 #[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
 pub const fn barfoo() {}
+
+// `rustc_const_stable` also requires the function to be stable.
+
+#[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
+const fn barfoo_unmarked() {}
+//~^ ERROR can only be applied to functions that are declared `#[stable]`
+
+#[unstable(feature = "unstable", issue = "none")]
+#[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
+pub const fn barfoo_unstable() {}
+//~^ ERROR can only be applied to functions that are declared `#[stable]`
+
+// `#[rustc_const_stable_indirect]` also requires a const fn
+#[rustc_const_stable_indirect]
+#[unstable(feature = "unstable", issue = "none")]
+pub fn not_a_const_fn() {}
+//~^ ERROR require the function or method to be `const`
diff --git a/tests/ui/consts/rustc-const-stability-require-const.stderr b/tests/ui/consts/rustc-const-stability-require-const.stderr
index 1027b9311b7..d9a7d37cbcd 100644
--- a/tests/ui/consts/rustc-const-stability-require-const.stderr
+++ b/tests/ui/consts/rustc-const-stability-require-const.stderr
@@ -1,8 +1,6 @@
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:7:1
    |
-LL | #[rustc_const_unstable(feature = "const_foo", issue = "none")]
-   | -------------------------------------------------------------- attribute specified here
 LL | pub fn foo() {}
    | ^^^^^^^^^^^^
    |
@@ -12,11 +10,9 @@ help: make the function or method const
 LL | pub fn foo() {}
    | ^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:12:1
    |
-LL | #[rustc_const_stable(feature = "const_bar", since = "1.0.0")]
-   | ------------------------------------------------------------- attribute specified here
 LL | pub fn bar() {}
    | ^^^^^^^^^^^^
    |
@@ -26,11 +22,9 @@ help: make the function or method const
 LL | pub fn bar() {}
    | ^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:21:5
    |
-LL |     #[rustc_const_unstable(feature = "const_salad", issue = "none")]
-   |     ---------------------------------------------------------------- attribute specified here
 LL |     pub fn salad(&self) -> &'static str { "mmmmmm" }
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
@@ -40,11 +34,9 @@ help: make the function or method const
 LL |     pub fn salad(&self) -> &'static str { "mmmmmm" }
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:26:5
    |
-LL |     #[rustc_const_unstable(feature = "const_roasted", issue = "none")]
-   |     ------------------------------------------------------------------ attribute specified here
 LL |     pub fn roasted(&self) -> &'static str { "mmmmmmmmmm" }
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
@@ -54,11 +46,9 @@ help: make the function or method const
 LL |     pub fn roasted(&self) -> &'static str { "mmmmmmmmmm" }
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:32:1
    |
-LL | #[rustc_const_stable(feature = "const_bar", since = "1.0.0")]
-   | ------------------------------------------------------------- attribute specified here
 LL | pub extern "C" fn bar_c() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
@@ -68,11 +58,9 @@ help: make the function or method const
 LL | pub extern "C" fn bar_c() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const`
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
   --> $DIR/rustc-const-stability-require-const.rs:37:1
    |
-LL | #[rustc_const_unstable(feature = "const_foo", issue = "none")]
-   | -------------------------------------------------------------- attribute specified here
 LL | pub extern "C" fn foo_c() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
@@ -82,5 +70,33 @@ help: make the function or method const
 LL | pub extern "C" fn foo_c() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 6 previous errors
+error: attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]`
+  --> $DIR/rustc-const-stability-require-const.rs:52:1
+   |
+LL | #[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
+   | ---------------------------------------------------------------- attribute specified here
+LL | const fn barfoo_unmarked() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]`
+  --> $DIR/rustc-const-stability-require-const.rs:57:1
+   |
+LL | #[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
+   | ---------------------------------------------------------------- attribute specified here
+LL | pub const fn barfoo_unstable() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
+  --> $DIR/rustc-const-stability-require-const.rs:63:1
+   |
+LL | pub fn not_a_const_fn() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function or method const
+  --> $DIR/rustc-const-stability-require-const.rs:63:1
+   |
+LL | pub fn not_a_const_fn() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 9 previous errors
 
diff --git a/tests/ui/consts/unstable-const-stable.rs b/tests/ui/consts/unstable-const-stable.rs
deleted file mode 100644
index f69e8d0efe5..00000000000
--- a/tests/ui/consts/unstable-const-stable.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-//@ aux-build:unstable_but_const_stable.rs
-
-extern crate unstable_but_const_stable;
-use unstable_but_const_stable::*;
-
-fn main() {
-    some_unstable_fn(); //~ERROR use of unstable library feature
-    unsafe { write_bytes(4 as *mut u8, 0, 0) }; //~ERROR use of unstable library feature
-}
-
-const fn const_main() {
-    some_unstable_fn(); //~ERROR use of unstable library feature
-    unsafe { write_bytes(4 as *mut u8, 0, 0) }; //~ERROR use of unstable library feature
-}
diff --git a/tests/ui/consts/unstable-const-stable.stderr b/tests/ui/consts/unstable-const-stable.stderr
deleted file mode 100644
index c4ffbbb60db..00000000000
--- a/tests/ui/consts/unstable-const-stable.stderr
+++ /dev/null
@@ -1,43 +0,0 @@
-error[E0658]: use of unstable library feature 'unstable'
-  --> $DIR/unstable-const-stable.rs:7:5
-   |
-LL |     some_unstable_fn();
-   |     ^^^^^^^^^^^^^^^^
-   |
-   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
-   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
-
-error[E0658]: use of unstable library feature 'unstable'
-  --> $DIR/unstable-const-stable.rs:8:14
-   |
-LL |     unsafe { write_bytes(4 as *mut u8, 0, 0) };
-   |              ^^^^^^^^^^^
-   |
-   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
-   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
-
-error[E0658]: use of unstable library feature 'unstable'
-  --> $DIR/unstable-const-stable.rs:12:5
-   |
-LL |     some_unstable_fn();
-   |     ^^^^^^^^^^^^^^^^
-   |
-   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
-   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
-
-error[E0658]: use of unstable library feature 'unstable'
-  --> $DIR/unstable-const-stable.rs:13:14
-   |
-LL |     unsafe { write_bytes(4 as *mut u8, 0, 0) };
-   |              ^^^^^^^^^^^
-   |
-   = note: see issue #42 <https://github.com/rust-lang/rust/issues/42> for more information
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
-   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
-
-error: aborting due to 4 previous errors
-
-For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
index 319056a9c88..d599523c727 100644
--- a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
+++ b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
@@ -1,31 +1,31 @@
 error: can't mark as unstable using an already stable feature
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
    |
-LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | const fn my_fun() {}
    | -------------------- the stability attribute annotates this item
    |
 help: consider removing the attribute
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
    |
-LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: can't mark as unstable using an already stable feature
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
    |
+LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | const fn my_fun() {}
    | -------------------- the stability attribute annotates this item
    |
 help: consider removing the attribute
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
    |
-LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/intrinsics/const-eval-select-stability.rs b/tests/ui/intrinsics/const-eval-select-stability.rs
index 575bc0cadda..25cbadaa22d 100644
--- a/tests/ui/intrinsics/const-eval-select-stability.rs
+++ b/tests/ui/intrinsics/const-eval-select-stability.rs
@@ -15,7 +15,7 @@ const fn nothing(){}
 #[rustc_const_stable(since = "1.0", feature = "const_hey")]
 pub const fn hey() {
     const_eval_select((), nothing, log);
-    //~^ ERROR `const_eval_select` is not yet stable as a const fn
+    //~^ ERROR cannot use `#[feature(const_eval_select)]`
 }
 
 fn main() {}
diff --git a/tests/ui/intrinsics/const-eval-select-stability.stderr b/tests/ui/intrinsics/const-eval-select-stability.stderr
index 335b9877aa0..5f443b1d4ff 100644
--- a/tests/ui/intrinsics/const-eval-select-stability.stderr
+++ b/tests/ui/intrinsics/const-eval-select-stability.stderr
@@ -1,10 +1,19 @@
-error: `const_eval_select` is not yet stable as a const fn
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(const_eval_select)]`
   --> $DIR/const-eval-select-stability.rs:17:5
    |
 LL |     const_eval_select((), nothing, log);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | pub const fn hey() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(const_eval_select)]
+LL | pub const fn hey() {
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-missing.rs b/tests/ui/stability-attribute/const-stability-attribute-implies-missing.rs
index 4089ec72885..6d6d793c62b 100644
--- a/tests/ui/stability-attribute/const-stability-attribute-implies-missing.rs
+++ b/tests/ui/stability-attribute/const-stability-attribute-implies-missing.rs
@@ -14,4 +14,3 @@ pub const fn foobar() -> u32 {
 }
 
 const VAR: u32 = foobar();
-//~^ ERROR: `foobar` is not yet stable as a const fn
diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-missing.stderr b/tests/ui/stability-attribute/const-stability-attribute-implies-missing.stderr
index 918d6ebf992..232de41c769 100644
--- a/tests/ui/stability-attribute/const-stability-attribute-implies-missing.stderr
+++ b/tests/ui/stability-attribute/const-stability-attribute-implies-missing.stderr
@@ -4,13 +4,5 @@ error: feature `const_bar` implying `const_foobar` does not exist
 LL | #[rustc_const_unstable(feature = "const_foobar", issue = "1", implied_by = "const_bar")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: `foobar` is not yet stable as a const fn
-  --> $DIR/const-stability-attribute-implies-missing.rs:16:18
-   |
-LL | const VAR: u32 = foobar();
-   |                  ^^^^^^^^
-   |
-   = help: add `#![feature(const_foobar)]` to the crate attributes to enable
-
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
diff --git a/tests/ui/stability-attribute/missing-const-stability.rs b/tests/ui/stability-attribute/missing-const-stability.rs
index 82da18cc9ac..f1139652550 100644
--- a/tests/ui/stability-attribute/missing-const-stability.rs
+++ b/tests/ui/stability-attribute/missing-const-stability.rs
@@ -1,6 +1,6 @@
 //@ compile-flags: -Znext-solver
 #![feature(staged_api)]
-#![feature(const_trait_impl, effects)] //~ WARN the feature `effects` is incomplete
+#![feature(const_trait_impl, effects, rustc_attrs, intrinsics)] //~ WARN the feature `effects` is incomplete
 #![stable(feature = "stable", since = "1.0.0")]
 
 #[stable(feature = "stable", since = "1.0.0")]
@@ -31,4 +31,15 @@ impl const Bar for Foo {
     fn fun() {}
 }
 
+#[stable(feature = "stable", since = "1.0.0")]
+#[rustc_intrinsic]
+pub const unsafe fn size_of_val<T>(x: *const T) -> usize { 42 }
+//~^ ERROR function has missing const stability attribute
+
+extern "rust-intrinsic" {
+    #[stable(feature = "stable", since = "1.0.0")]
+    #[rustc_const_stable_indirect]
+    pub fn min_align_of_val<T>(x: *const T) -> usize;
+}
+
 fn main() {}
diff --git a/tests/ui/stability-attribute/missing-const-stability.stderr b/tests/ui/stability-attribute/missing-const-stability.stderr
index adf60c38611..e62a8b88261 100644
--- a/tests/ui/stability-attribute/missing-const-stability.stderr
+++ b/tests/ui/stability-attribute/missing-const-stability.stderr
@@ -1,7 +1,7 @@
 warning: the feature `effects` is incomplete and may not be safe to use and/or cause compiler crashes
   --> $DIR/missing-const-stability.rs:3:30
    |
-LL | #![feature(const_trait_impl, effects)]
+LL | #![feature(const_trait_impl, effects, rustc_attrs, intrinsics)]
    |                              ^^^^^^^
    |
    = note: see issue #102090 <https://github.com/rust-lang/rust/issues/102090> for more information
@@ -22,11 +22,17 @@ LL | |     fn fun() {}
 LL | | }
    | |_^
 
+error: function has missing const stability attribute
+  --> $DIR/missing-const-stability.rs:36:1
+   |
+LL | pub const unsafe fn size_of_val<T>(x: *const T) -> usize { 42 }
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: associated function has missing const stability attribute
   --> $DIR/missing-const-stability.rs:16:5
    |
 LL |     pub const fn foo() {}
    |     ^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 3 previous errors; 1 warning emitted
+error: aborting due to 4 previous errors; 1 warning emitted
 
diff --git a/tests/ui/target-feature/feature-hierarchy.rs b/tests/ui/target-feature/feature-hierarchy.rs
index 4cf9112810c..7f14d700ecb 100644
--- a/tests/ui/target-feature/feature-hierarchy.rs
+++ b/tests/ui/target-feature/feature-hierarchy.rs
@@ -19,6 +19,7 @@ trait Copy {}
 impl Copy for bool {}
 
 extern "rust-intrinsic" {
+    #[stable(feature = "test", since = "1.0.0")]
     #[rustc_const_stable(feature = "test", since = "1.0.0")]
     fn unreachable() -> !;
 }
diff --git a/tests/ui/target-feature/no-llvm-leaks.rs b/tests/ui/target-feature/no-llvm-leaks.rs
index 9f5dec4447f..f0c887bc1e0 100644
--- a/tests/ui/target-feature/no-llvm-leaks.rs
+++ b/tests/ui/target-feature/no-llvm-leaks.rs
@@ -17,6 +17,7 @@ trait Copy {}
 impl Copy for bool {}
 
 extern "rust-intrinsic" {
+    #[stable(feature = "test", since = "1.0.0")]
     #[rustc_const_stable(feature = "test", since = "1.0.0")]
     fn unreachable() -> !;
 }
diff --git a/tests/ui/traits/const-traits/auxiliary/staged-api.rs b/tests/ui/traits/const-traits/auxiliary/staged-api.rs
index 986165ef91e..bb591321b84 100644
--- a/tests/ui/traits/const-traits/auxiliary/staged-api.rs
+++ b/tests/ui/traits/const-traits/auxiliary/staged-api.rs
@@ -19,3 +19,12 @@ pub struct Unstable;
 impl const MyTrait for Unstable {
     fn func() {}
 }
+
+#[stable(feature = "rust1", since = "1.0.0")]
+pub struct Unstable2;
+
+#[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "unstable2", issue = "none")]
+impl const MyTrait for Unstable2 {
+    fn func() {}
+}
diff --git a/tests/ui/traits/const-traits/effects/minicore.rs b/tests/ui/traits/const-traits/effects/minicore.rs
index a756f4d9f6c..1f0d22eeb38 100644
--- a/tests/ui/traits/const-traits/effects/minicore.rs
+++ b/tests/ui/traits/const-traits/effects/minicore.rs
@@ -515,7 +515,7 @@ trait StructuralPartialEq {}
 
 const fn drop<T: ~const Destruct>(_: T) {}
 
-#[rustc_const_stable(feature = "const_eval_select", since = "1.0.0")]
+#[rustc_const_stable_indirect]
 #[rustc_intrinsic_must_be_overridden]
 #[rustc_intrinsic]
 const fn const_eval_select<ARG: Tuple, F, G, RET>(
diff --git a/tests/ui/traits/const-traits/issue-79450.rs b/tests/ui/traits/const-traits/issue-79450.rs
index b8b9e07b3bd..cdefebc87d6 100644
--- a/tests/ui/traits/const-traits/issue-79450.rs
+++ b/tests/ui/traits/const-traits/issue-79450.rs
@@ -1,6 +1,5 @@
 //@ compile-flags: -Znext-solver
 #![allow(incomplete_features)]
-#![feature(const_fmt_arguments_new)]
 #![feature(const_trait_impl, effects)]
 
 #[const_trait]
diff --git a/tests/ui/traits/const-traits/issue-79450.stderr b/tests/ui/traits/const-traits/issue-79450.stderr
index 9e6348d37ed..49f380c1a2b 100644
--- a/tests/ui/traits/const-traits/issue-79450.stderr
+++ b/tests/ui/traits/const-traits/issue-79450.stderr
@@ -1,5 +1,5 @@
 error[E0015]: cannot call non-const fn `_print` in constant functions
-  --> $DIR/issue-79450.rs:11:9
+  --> $DIR/issue-79450.rs:10:9
    |
 LL |         println!("lul");
    |         ^^^^^^^^^^^^^^^
diff --git a/tests/ui/traits/const-traits/staged-api.rs b/tests/ui/traits/const-traits/staged-api.rs
index f87e723472a..59fe6d52d5d 100644
--- a/tests/ui/traits/const-traits/staged-api.rs
+++ b/tests/ui/traits/const-traits/staged-api.rs
@@ -2,6 +2,7 @@
 //@ compile-flags: -Znext-solver
 
 #![cfg_attr(unstable, feature(unstable))] // The feature from the ./auxiliary/staged-api.rs file.
+#![cfg_attr(unstable, feature(local_feature))]
 #![feature(const_trait_impl, effects)]
 #![allow(incomplete_features)]
 #![feature(staged_api)]
@@ -16,8 +17,8 @@ use staged_api::*;
 pub struct Foo;
 
 #[stable(feature = "rust1", since = "1.0.0")]
-#[cfg_attr(unstable, rustc_const_unstable(feature = "foo", issue = "none"))]
-#[cfg_attr(stable, rustc_const_stable(feature = "foo", since = "1.0.0"))]
+#[cfg_attr(unstable, rustc_const_unstable(feature = "local_feature", issue = "none"))]
+#[cfg_attr(stable, rustc_const_stable(feature = "local_feature", since = "1.0.0"))]
 impl const MyTrait for Foo {
     //[stable]~^ ERROR trait implementations cannot be const stable yet
     fn func() {}
@@ -32,32 +33,43 @@ fn non_const_context() {
 #[unstable(feature = "none", issue = "none")]
 const fn const_context() {
     Unstable::func();
-    //[stable]~^ ERROR not yet stable as a const fn
+    //[unstable]~^ ERROR cannot use `#[feature(unstable)]`
+    //[stable]~^^ ERROR not yet stable as a const fn
     Foo::func();
-    //[unstable]~^ ERROR not yet stable as a const fn
-    // ^ fails, because the `foo` feature is not active
+    //[unstable]~^ ERROR cannot use `#[feature(local_feature)]`
+    //[stable]~^^ cannot be (indirectly) exposed to stable
+    // We get the error on `stable` since this is a trait function.
+    Unstable2::func();
+    //~^ ERROR not yet stable as a const fn
+    // ^ fails, because the `unstable2` feature is not active
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
-#[cfg_attr(unstable, rustc_const_unstable(feature = "foo", issue = "none"))]
+#[cfg_attr(unstable, rustc_const_unstable(feature = "local_feature", issue = "none"))]
 pub const fn const_context_not_const_stable() {
     //[stable]~^ ERROR function has missing const stability attribute
     Unstable::func();
     //[stable]~^ ERROR not yet stable as a const fn
     Foo::func();
-    //[unstable]~^ ERROR not yet stable as a const fn
-    // ^ fails, because the `foo` feature is not active
+    //[stable]~^ cannot be (indirectly) exposed to stable
+    // We get the error on `stable` since this is a trait function.
+    Unstable2::func();
+    //~^ ERROR not yet stable as a const fn
+    // ^ fails, because the `unstable2` feature is not active
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_const_stable(feature = "cheese", since = "1.0.0")]
 const fn stable_const_context() {
     Unstable::func();
-    //~^ ERROR not yet stable as a const fn
+    //[unstable]~^ ERROR cannot use `#[feature(unstable)]`
+    //[stable]~^^ ERROR not yet stable as a const fn
     Foo::func();
-    //[unstable]~^ ERROR not yet stable as a const fn
+    //[unstable]~^ ERROR cannot use `#[feature(local_feature)]`
+    //[stable]~^^ cannot be (indirectly) exposed to stable
+    // We get the error on `stable` since this is a trait function.
     const_context_not_const_stable()
-    //[unstable]~^ ERROR not yet stable as a const fn
+    //[unstable]~^ ERROR cannot use `#[feature(local_feature)]`
 }
 
 fn main() {}
diff --git a/tests/ui/traits/const-traits/staged-api.stable.stderr b/tests/ui/traits/const-traits/staged-api.stable.stderr
index 6c07a253f5b..40045081f93 100644
--- a/tests/ui/traits/const-traits/staged-api.stable.stderr
+++ b/tests/ui/traits/const-traits/staged-api.stable.stderr
@@ -1,5 +1,5 @@
 error: trait implementations cannot be const stable yet
-  --> $DIR/staged-api.rs:21:1
+  --> $DIR/staged-api.rs:22:1
    |
 LL | / impl const MyTrait for Foo {
 LL | |
@@ -10,40 +10,80 @@ LL | | }
    = note: see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information
 
 error: function has missing const stability attribute
-  --> $DIR/staged-api.rs:43:1
+  --> $DIR/staged-api.rs:49:1
    |
 LL | / pub const fn const_context_not_const_stable() {
 LL | |
 LL | |     Unstable::func();
 LL | |
 ...  |
-LL | |     // ^ fails, because the `foo` feature is not active
+LL | |     // ^ fails, because the `unstable2` feature is not active
 LL | | }
    | |_^
 
 error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:34:5
+  --> $DIR/staged-api.rs:35:5
    |
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
    = help: add `#![feature(unstable)]` to the crate attributes to enable
 
+error: `<Foo as staged_api::MyTrait>::func` cannot be (indirectly) exposed to stable
+  --> $DIR/staged-api.rs:38:5
+   |
+LL |     Foo::func();
+   |     ^^^^^^^^^^^
+   |
+   = help: either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
+
+error: `<staged_api::Unstable2 as staged_api::MyTrait>::func` is not yet stable as a const fn
+  --> $DIR/staged-api.rs:42:5
+   |
+LL |     Unstable2::func();
+   |     ^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable2)]` to the crate attributes to enable
+
 error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:45:5
+  --> $DIR/staged-api.rs:51:5
    |
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
    = help: add `#![feature(unstable)]` to the crate attributes to enable
 
+error: `<Foo as staged_api::MyTrait>::func` cannot be (indirectly) exposed to stable
+  --> $DIR/staged-api.rs:53:5
+   |
+LL |     Foo::func();
+   |     ^^^^^^^^^^^
+   |
+   = help: either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
+
+error: `<staged_api::Unstable2 as staged_api::MyTrait>::func` is not yet stable as a const fn
+  --> $DIR/staged-api.rs:56:5
+   |
+LL |     Unstable2::func();
+   |     ^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable2)]` to the crate attributes to enable
+
 error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:55:5
+  --> $DIR/staged-api.rs:64:5
    |
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
+
+error: `<Foo as staged_api::MyTrait>::func` cannot be (indirectly) exposed to stable
+  --> $DIR/staged-api.rs:67:5
+   |
+LL |     Foo::func();
+   |     ^^^^^^^^^^^
+   |
+   = help: either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
 
-error: aborting due to 5 previous errors
+error: aborting due to 10 previous errors
 
diff --git a/tests/ui/traits/const-traits/staged-api.unstable.stderr b/tests/ui/traits/const-traits/staged-api.unstable.stderr
index 1c772f13dd5..64b3a8ab19f 100644
--- a/tests/ui/traits/const-traits/staged-api.unstable.stderr
+++ b/tests/ui/traits/const-traits/staged-api.unstable.stderr
@@ -1,42 +1,108 @@
-error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:36:5
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(unstable)]`
+  --> $DIR/staged-api.rs:35:5
    |
-LL |     Foo::func();
-   |     ^^^^^^^^^^^
+LL |     Unstable::func();
+   |     ^^^^^^^^^^^^^^^^
+   |
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn const_context() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(unstable)]
+LL | const fn const_context() {
    |
-   = help: add `#![feature(foo)]` to the crate attributes to enable
 
-error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:47:5
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local_feature)]`
+  --> $DIR/staged-api.rs:38:5
    |
 LL |     Foo::func();
    |     ^^^^^^^^^^^
    |
-   = help: add `#![feature(foo)]` to the crate attributes to enable
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn const_context() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local_feature)]
+LL | const fn const_context() {
+   |
+
+error: `<staged_api::Unstable2 as staged_api::MyTrait>::func` is not yet stable as a const fn
+  --> $DIR/staged-api.rs:42:5
+   |
+LL |     Unstable2::func();
+   |     ^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable2)]` to the crate attributes to enable
 
-error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:55:5
+error: `<staged_api::Unstable2 as staged_api::MyTrait>::func` is not yet stable as a const fn
+  --> $DIR/staged-api.rs:56:5
+   |
+LL |     Unstable2::func();
+   |     ^^^^^^^^^^^^^^^^^
+   |
+   = help: add `#![feature(unstable2)]` to the crate attributes to enable
+
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(unstable)]`
+  --> $DIR/staged-api.rs:64:5
    |
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn stable_const_context() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(unstable)]
+LL | const fn stable_const_context() {
+   |
 
-error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:57:5
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local_feature)]`
+  --> $DIR/staged-api.rs:67:5
    |
 LL |     Foo::func();
    |     ^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn stable_const_context() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local_feature)]
+LL | const fn stable_const_context() {
+   |
 
-error: `const_context_not_const_stable` is not yet stable as a const fn
-  --> $DIR/staged-api.rs:59:5
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local_feature)]`
+  --> $DIR/staged-api.rs:71:5
    |
 LL |     const_context_not_const_stable()
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: const-stable functions can only call other const-stable functions
+   = help: mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
+help: if the caller is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn stable_const_context() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local_feature)]
+LL | const fn stable_const_context() {
+   |
 
-error: aborting due to 5 previous errors
+error: aborting due to 7 previous errors