about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-05 00:13:01 +0000
committerbors <bors@rust-lang.org>2024-03-05 00:13:01 +0000
commit2eeff462b762ed00f9d557d8c6ad7a3d562f692a (patch)
treed5e2442dec8294e89a12d0680109e0ef3fd8d7cf
parent50e77f133f8eb1f745e05681163a0143d6c4dd7d (diff)
parentc04f0caaff5b09a2202bb220579ef45722e74ed2 (diff)
downloadrust-2eeff462b762ed00f9d557d8c6ad7a3d562f692a.tar.gz
rust-2eeff462b762ed00f9d557d8c6ad7a3d562f692a.zip
Auto merge of #120675 - oli-obk:intrinsics3.0, r=pnkfelix
Add a scheme for moving away from `extern "rust-intrinsic"` entirely

All `rust-intrinsic`s can become free functions now, either with a fallback body, or with a dummy body and an attribute, requiring backends to actually implement the intrinsic.

This PR demonstrates the dummy-body scheme with the `vtable_size` intrinsic.

cc https://github.com/rust-lang/rust/issues/63585

follow-up to #120500

MCP at https://github.com/rust-lang/compiler-team/issues/720
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs2
-rw-r--r--compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs12
-rw-r--r--compiler/rustc_codegen_ssa/src/back/symbol_export.rs4
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs59
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_hir_analysis/src/check/check.rs4
-rw-r--r--compiler/rustc_hir_typeck/src/callee.rs2
-rw-r--r--compiler/rustc_lint/src/builtin.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs11
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/query/erase.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs2
-rw-r--r--compiler/rustc_middle/src/ty/intrinsic.rs17
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs2
-rw-r--r--compiler/rustc_middle/src/ty/parameterized.rs1
-rw-r--r--compiler/rustc_middle/src/ty/util.rs15
-rw-r--r--compiler/rustc_mir_dataflow/src/rustc_peek.rs2
-rw-r--r--compiler/rustc_mir_transform/src/cross_crate_inline.rs4
-rw-r--r--compiler/rustc_mir_transform/src/instsimplify.rs4
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs8
-rw-r--r--compiler/rustc_mir_transform/src/lower_intrinsics.rs10
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs5
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_ty_utils/src/instance.rs4
-rw-r--r--library/core/src/intrinsics.rs14
-rw-r--r--src/doc/unstable-book/src/language-features/intrinsics.md13
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs2
-rw-r--r--tests/ui/intrinsics/always-gets-overridden.rs20
-rw-r--r--tests/ui/intrinsics/not-overridden.rs18
-rw-r--r--tests/ui/intrinsics/not-overridden.stderr10
31 files changed, 207 insertions, 51 deletions
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 26e1e24d1a1..0f3e995a331 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -1667,7 +1667,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             // (Eventually this should use const-generics, but those are not up for the task yet:
             // https://github.com/rust-lang/rust/issues/85229.)
             if let Some(name @ (sym::simd_shuffle | sym::simd_insert | sym::simd_extract)) =
-                self.tcx().intrinsic(def_id)
+                self.tcx().intrinsic(def_id).map(|i| i.name)
             {
                 let idx = match name {
                     sym::simd_shuffle => 2,
diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
index 84269ec2942..9b8167fa2bf 100644
--- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
+++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
@@ -1255,7 +1255,17 @@ fn codegen_regular_intrinsic_call<'tcx>(
 
         // Unimplemented intrinsics must have a fallback body. The fallback body is obtained
         // by converting the `InstanceDef::Intrinsic` to an `InstanceDef::Item`.
-        _ => return Err(Instance::new(instance.def_id(), instance.args)),
+        _ => {
+            let intrinsic = fx.tcx.intrinsic(instance.def_id()).unwrap();
+            if intrinsic.must_be_overridden {
+                span_bug!(
+                    source_info.span,
+                    "intrinsic {} must be overridden by codegen_cranelift, but isn't",
+                    intrinsic.name,
+                );
+            }
+            return Err(Instance::new(instance.def_id(), instance.args));
+        }
     }
 
     let ret_block = fx.get_block(destination.unwrap());
diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
index 2dba04e0bb7..bff7e43b8e4 100644
--- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
+++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
@@ -81,6 +81,10 @@ fn reachable_non_generics_provider(tcx: TyCtxt<'_>, _: LocalCrate) -> DefIdMap<S
                 return library.kind.is_statically_included().then_some(def_id);
             }
 
+            if tcx.intrinsic(def_id).is_some_and(|i| i.must_be_overridden) {
+                return None;
+            }
+
             // Only consider nodes that actually have exported symbols.
             match tcx.def_kind(def_id) {
                 DefKind::Fn | DefKind::Static(_) => {}
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index 9c7aadb81f8..95a587b8181 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -12,12 +12,12 @@ use crate::MemFlags;
 use rustc_ast as ast;
 use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
 use rustc_hir::lang_items::LangItem;
-use rustc_middle::mir::{self, AssertKind, SwitchTargets, UnwindTerminateReason};
+use rustc_middle::mir::{self, AssertKind, BasicBlock, SwitchTargets, UnwindTerminateReason};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
 use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
 use rustc_middle::ty::{self, Instance, Ty};
 use rustc_session::config::OptLevel;
-use rustc_span::{source_map::Spanned, sym, Span, Symbol};
+use rustc_span::{source_map::Spanned, sym, Span};
 use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg};
 use rustc_target::abi::{self, HasDataLayout, WrappingRange};
 use rustc_target::spec::abi::Abi;
@@ -680,7 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         &mut self,
         helper: &TerminatorCodegenHelper<'tcx>,
         bx: &mut Bx,
-        intrinsic: Option<Symbol>,
+        intrinsic: Option<ty::IntrinsicDef>,
         instance: Option<Instance<'tcx>>,
         source_info: mir::SourceInfo,
         target: Option<mir::BasicBlock>,
@@ -690,7 +690,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         // Emit a panic or a no-op for `assert_*` intrinsics.
         // These are intrinsics that compile to panics so that we can get a message
         // which mentions the offending type, even from a const context.
-        let panic_intrinsic = intrinsic.and_then(|s| ValidityRequirement::from_intrinsic(s));
+        let panic_intrinsic = intrinsic.and_then(|i| ValidityRequirement::from_intrinsic(i.name));
         if let Some(requirement) = panic_intrinsic {
             let ty = instance.unwrap().args.type_at(0);
 
@@ -826,14 +826,20 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         // The arguments we'll be passing. Plus one to account for outptr, if used.
         let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize;
 
-        if intrinsic == Some(sym::caller_location) {
+        if matches!(intrinsic, Some(ty::IntrinsicDef { name: sym::caller_location, .. })) {
             return if let Some(target) = target {
                 let location =
                     self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info });
 
                 let mut llargs = Vec::with_capacity(arg_count);
-                let ret_dest =
-                    self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs, true, true);
+                let ret_dest = self.make_return_dest(
+                    bx,
+                    destination,
+                    &fn_abi.ret,
+                    &mut llargs,
+                    intrinsic,
+                    Some(target),
+                );
                 assert_eq!(llargs, []);
                 if let ReturnDest::IndirectOperand(tmp, _) = ret_dest {
                     location.val.store(bx, tmp);
@@ -846,7 +852,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         }
 
         let instance = match intrinsic {
-            None | Some(sym::drop_in_place) => instance,
+            None | Some(ty::IntrinsicDef { name: sym::drop_in_place, .. }) => instance,
             Some(intrinsic) => {
                 let mut llargs = Vec::with_capacity(1);
                 let ret_dest = self.make_return_dest(
@@ -854,8 +860,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     destination,
                     &fn_abi.ret,
                     &mut llargs,
-                    true,
-                    target.is_some(),
+                    Some(intrinsic),
+                    target,
                 );
                 let dest = match ret_dest {
                     _ if fn_abi.ret.is_indirect() => llargs[0],
@@ -873,7 +879,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         // The indices passed to simd_shuffle in the
                         // third argument must be constant. This is
                         // checked by the type-checker.
-                        if i == 2 && intrinsic == sym::simd_shuffle {
+                        if i == 2 && intrinsic.name == sym::simd_shuffle {
                             if let mir::Operand::Constant(constant) = &arg.node {
                                 let (llval, ty) = self.simd_shuffle_indices(bx, constant);
                                 return OperandRef {
@@ -903,14 +909,33 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             MergingSucc::False
                         };
                     }
-                    Err(instance) => Some(instance),
+                    Err(instance) => {
+                        if intrinsic.must_be_overridden {
+                            span_bug!(
+                                span,
+                                "intrinsic {} must be overridden by codegen backend, but isn't",
+                                intrinsic.name,
+                            );
+                        }
+                        Some(instance)
+                    }
                 }
             }
         };
 
         let mut llargs = Vec::with_capacity(arg_count);
         let destination = target.as_ref().map(|&target| {
-            (self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs, false, true), target)
+            (
+                self.make_return_dest(
+                    bx,
+                    destination,
+                    &fn_abi.ret,
+                    &mut llargs,
+                    None,
+                    Some(target),
+                ),
+                target,
+            )
         });
 
         // Split the rust-call tupled arguments off.
@@ -1643,10 +1668,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         dest: mir::Place<'tcx>,
         fn_ret: &ArgAbi<'tcx, Ty<'tcx>>,
         llargs: &mut Vec<Bx::Value>,
-        is_intrinsic: bool,
-        has_target: bool,
+        intrinsic: Option<ty::IntrinsicDef>,
+        target: Option<BasicBlock>,
     ) -> ReturnDest<'tcx, Bx::Value> {
-        if !has_target {
+        if target.is_none() {
             return ReturnDest::Nothing;
         }
         // If the return is ignored, we can just return a do-nothing `ReturnDest`.
@@ -1667,7 +1692,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         tmp.storage_live(bx);
                         llargs.push(tmp.llval);
                         ReturnDest::IndirectOperand(tmp, index)
-                    } else if is_intrinsic {
+                    } else if intrinsic.is_some() {
                         // Currently, intrinsics always need a location to store
                         // the result, so we create a temporary `alloca` for the
                         // result.
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 10867ae230a..de7ea84ffa5 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -867,6 +867,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         rustc_no_mir_inline, Normal, template!(Word), WarnFollowing,
         "#[rustc_no_mir_inline] prevents the MIR inliner from inlining a function while not affecting codegen"
     ),
+    rustc_attr!(
+        rustc_intrinsic_must_be_overridden, Normal, template!(Word), ErrorFollowing,
+        "the `#[rustc_intrinsic_must_be_overridden]` attribute is used to declare intrinsics without real bodies",
+    ),
 
     // ==========================================================================
     // Internal attributes, Testing:
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index 96bebda5828..9421d9b5ae0 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -527,12 +527,12 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
             check_enum(tcx, def_id);
         }
         DefKind::Fn => {
-            if let Some(name) = tcx.intrinsic(def_id) {
+            if let Some(i) = tcx.intrinsic(def_id) {
                 intrinsic::check_intrinsic_type(
                     tcx,
                     def_id,
                     tcx.def_ident_span(def_id).unwrap(),
-                    name,
+                    i.name,
                     Abi::Rust,
                 )
             }
diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs
index b87d71e3533..25d34531379 100644
--- a/compiler/rustc_hir_typeck/src/callee.rs
+++ b/compiler/rustc_hir_typeck/src/callee.rs
@@ -545,7 +545,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
         if let Some(def_id) = def_id
             && self.tcx.def_kind(def_id) == hir::def::DefKind::Fn
-            && matches!(self.tcx.intrinsic(def_id), Some(sym::const_eval_select))
+            && self.tcx.is_intrinsic(def_id, sym::const_eval_select)
         {
             let fn_sig = self.resolve_vars_if_possible(fn_sig);
             for idx in 0..=1 {
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 8d61d297839..65a0ce96a64 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -1231,7 +1231,7 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
         }
 
         fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
-            matches!(cx.tcx.intrinsic(def_id), Some(sym::transmute))
+            cx.tcx.is_intrinsic(def_id, sym::transmute)
         }
     }
 }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 09cb6b9fa0a..82cceeae7e7 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -1749,7 +1749,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
         self.root.tables.attr_flags.get(self, index)
     }
 
-    fn get_intrinsic(self, index: DefIndex) -> Option<Symbol> {
+    fn get_intrinsic(self, index: DefIndex) -> Option<ty::IntrinsicDef> {
         self.root.tables.intrinsic.get(self, index).map(|d| d.decode(self))
     }
 
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 7d3f84060f6..04a872d65c4 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -1053,11 +1053,14 @@ fn should_encode_mir(
         // Full-fledged functions + closures
         DefKind::AssocFn | DefKind::Fn | DefKind::Closure => {
             let generics = tcx.generics_of(def_id);
-            let opt = tcx.sess.opts.unstable_opts.always_encode_mir
+            let mut opt = tcx.sess.opts.unstable_opts.always_encode_mir
                 || (tcx.sess.opts.output_types.should_codegen()
                     && reachable_set.contains(&def_id)
                     && (generics.requires_monomorphization(tcx)
                         || tcx.cross_crate_inlinable(def_id)));
+            if let Some(intrinsic) = tcx.intrinsic(def_id) {
+                opt &= !intrinsic.must_be_overridden;
+            }
             // The function has a `const` modifier or is in a `#[const_trait]`.
             let is_const_fn = tcx.is_const_fn_raw(def_id.to_def_id())
                 || tcx.is_const_default_method(def_id.to_def_id());
@@ -1409,9 +1412,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             if let DefKind::Fn | DefKind::AssocFn = def_kind {
                 self.tables.asyncness.set_some(def_id.index, tcx.asyncness(def_id));
                 record_array!(self.tables.fn_arg_names[def_id] <- tcx.fn_arg_names(def_id));
-                if let Some(name) = tcx.intrinsic(def_id) {
-                    record!(self.tables.intrinsic[def_id] <- name);
-                }
+            }
+            if let Some(name) = tcx.intrinsic(def_id) {
+                record!(self.tables.intrinsic[def_id] <- name);
             }
             if let DefKind::TyParam = def_kind {
                 let default = self.tcx.object_lifetime_default(def_id);
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 9c0e8029571..a59028cec7a 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -375,7 +375,7 @@ macro_rules! define_tables {
 
 define_tables! {
 - defaulted:
-    intrinsic: Table<DefIndex, Option<LazyValue<Symbol>>>,
+    intrinsic: Table<DefIndex, Option<LazyValue<ty::IntrinsicDef>>>,
     is_macro_rules: Table<DefIndex, bool>,
     is_type_alias_impl_trait: Table<DefIndex, bool>,
     type_alias_is_lazy: Table<DefIndex, bool>,
diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs
index 2cdcdcb1492..d0711baa181 100644
--- a/compiler/rustc_middle/src/query/erase.rs
+++ b/compiler/rustc_middle/src/query/erase.rs
@@ -241,7 +241,7 @@ trivial! {
     Option<rustc_target::abi::FieldIdx>,
     Option<rustc_target::spec::PanicStrategy>,
     Option<usize>,
-    Option<rustc_span::Symbol>,
+    Option<rustc_middle::ty::IntrinsicDef>,
     Result<(), rustc_errors::ErrorGuaranteed>,
     Result<(), rustc_middle::traits::query::NoSolution>,
     Result<rustc_middle::traits::EvaluationResult, rustc_middle::traits::OverflowError>,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index e87bc581e6e..8357c21a3c2 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1760,7 +1760,7 @@ rustc_queries! {
         separate_provide_extern
     }
     /// Whether the function is an intrinsic
-    query intrinsic(def_id: DefId) -> Option<Symbol> {
+    query intrinsic(def_id: DefId) -> Option<rustc_middle::ty::IntrinsicDef> {
         desc { |tcx| "fetch intrinsic name if `{}` is an intrinsic", tcx.def_path_str(def_id) }
         separate_provide_extern
     }
diff --git a/compiler/rustc_middle/src/ty/intrinsic.rs b/compiler/rustc_middle/src/ty/intrinsic.rs
new file mode 100644
index 00000000000..18d08ed23a5
--- /dev/null
+++ b/compiler/rustc_middle/src/ty/intrinsic.rs
@@ -0,0 +1,17 @@
+use rustc_span::{def_id::DefId, Symbol};
+
+use super::TyCtxt;
+
+#[derive(Copy, Clone, Debug, Decodable, Encodable, HashStable)]
+pub struct IntrinsicDef {
+    pub name: Symbol,
+    /// Whether the intrinsic has no meaningful body and all backends need to shim all calls to it.
+    pub must_be_overridden: bool,
+}
+
+impl TyCtxt<'_> {
+    pub fn is_intrinsic(self, def_id: DefId, name: Symbol) -> bool {
+        let Some(i) = self.intrinsic(def_id) else { return false };
+        i.name == name
+    }
+}
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index f005a240504..c6bb7032ace 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -30,6 +30,7 @@ pub use adt::*;
 pub use assoc::*;
 pub use generic_args::*;
 pub use generics::*;
+pub use intrinsic::IntrinsicDef;
 use rustc_ast as ast;
 use rustc_ast::node_id::NodeMap;
 pub use rustc_ast_ir::{Movability, Mutability};
@@ -149,6 +150,7 @@ mod generic_args;
 mod generics;
 mod impls_ty;
 mod instance;
+mod intrinsic;
 mod list;
 mod opaque_types;
 mod parameterized;
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index 22f0574d614..571c6e918dc 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -75,6 +75,7 @@ trivially_parameterized_over_tcx! {
     ty::Visibility<DefIndex>,
     ty::adjustment::CoerceUnsizedInfo,
     ty::fast_reject::SimplifiedType,
+    ty::IntrinsicDef,
     rustc_ast::Attribute,
     rustc_ast::DelimArgs,
     rustc_ast::expand::StrippedCfgItem<rustc_hir::def_id::DefIndex>,
diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs
index 5ead620927c..a3c22db2add 100644
--- a/compiler/rustc_middle/src/ty/util.rs
+++ b/compiler/rustc_middle/src/ty/util.rs
@@ -19,7 +19,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
 use rustc_index::bit_set::GrowableBitSet;
 use rustc_macros::HashStable;
 use rustc_session::Limit;
-use rustc_span::{sym, Symbol};
+use rustc_span::sym;
 use rustc_target::abi::{Integer, IntegerType, Primitive, Size};
 use rustc_target::spec::abi::Abi;
 use smallvec::SmallVec;
@@ -1641,12 +1641,19 @@ pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
         .any(|items| items.iter().any(|item| item.has_name(sym::notable_trait)))
 }
 
-/// Determines whether an item is an intrinsic by Abi. or by whether it has a `rustc_intrinsic` attribute
-pub fn intrinsic(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Symbol> {
+/// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute)
+pub fn intrinsic(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ty::IntrinsicDef> {
+    match tcx.def_kind(def_id) {
+        DefKind::Fn | DefKind::AssocFn => {}
+        _ => return None,
+    }
     if matches!(tcx.fn_sig(def_id).skip_binder().abi(), Abi::RustIntrinsic)
         || tcx.has_attr(def_id, sym::rustc_intrinsic)
     {
-        Some(tcx.item_name(def_id.into()))
+        Some(ty::IntrinsicDef {
+            name: tcx.item_name(def_id.into()),
+            must_be_overridden: tcx.has_attr(def_id, sym::rustc_intrinsic_must_be_overridden),
+        })
     } else {
         None
     }
diff --git a/compiler/rustc_mir_dataflow/src/rustc_peek.rs b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
index 1575f31e75e..d7e3c91b875 100644
--- a/compiler/rustc_mir_dataflow/src/rustc_peek.rs
+++ b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
@@ -202,7 +202,7 @@ impl PeekCall {
             &terminator.kind
         {
             if let ty::FnDef(def_id, fn_args) = *func.const_.ty().kind() {
-                if tcx.intrinsic(def_id)? != sym::rustc_peek {
+                if tcx.intrinsic(def_id)?.name != sym::rustc_peek {
                     return None;
                 }
 
diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs
index 483fd753e70..07e6ecccaa4 100644
--- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs
+++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs
@@ -23,6 +23,10 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
         return false;
     }
 
+    if tcx.intrinsic(def_id).is_some_and(|i| i.must_be_overridden) {
+        return false;
+    }
+
     // This just reproduces the logic from Instance::requires_inline.
     match tcx.def_kind(def_id) {
         DefKind::Ctor(..) | DefKind::Closure => return true,
diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs
index 73102a5f026..6b33d81c1c4 100644
--- a/compiler/rustc_mir_transform/src/instsimplify.rs
+++ b/compiler/rustc_mir_transform/src/instsimplify.rs
@@ -323,8 +323,8 @@ fn resolve_rust_intrinsic<'tcx>(
     func_ty: Ty<'tcx>,
 ) -> Option<(Symbol, GenericArgsRef<'tcx>)> {
     if let ty::FnDef(def_id, args) = *func_ty.kind() {
-        let name = tcx.intrinsic(def_id)?;
-        return Some((name, args));
+        let intrinsic = tcx.intrinsic(def_id)?;
+        return Some((intrinsic.name, args));
     }
     None
 }
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 945c3c662a6..cd9b98e4f32 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -160,7 +160,7 @@ fn remap_mir_for_const_eval_select<'tcx>(
                 fn_span,
                 ..
             } if let ty::FnDef(def_id, _) = *const_.ty().kind()
-                && matches!(tcx.intrinsic(def_id), Some(sym::const_eval_select)) =>
+                && tcx.is_intrinsic(def_id, sym::const_eval_select) =>
             {
                 let [tupled_args, called_in_const, called_at_rt]: [_; 3] =
                     std::mem::take(args).try_into().unwrap();
@@ -632,6 +632,12 @@ fn optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> &Body<'_> {
 }
 
 fn inner_optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> Body<'_> {
+    if tcx.intrinsic(did).is_some_and(|i| i.must_be_overridden) {
+        span_bug!(
+            tcx.def_span(did),
+            "this intrinsic must be overridden by the codegen backend, it has no meaningful body",
+        )
+    }
     if tcx.is_constructor(did.to_def_id()) {
         // There's no reason to run all of the MIR passes on constructors when
         // we can just output the MIR we want directly. This also saves const
diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs
index a0af902c4e1..f317c025e96 100644
--- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs
+++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs
@@ -14,9 +14,9 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
             if let TerminatorKind::Call { func, args, destination, target, .. } =
                 &mut terminator.kind
                 && let ty::FnDef(def_id, generic_args) = *func.ty(local_decls, tcx).kind()
-                && let Some(intrinsic_name) = tcx.intrinsic(def_id)
+                && let Some(intrinsic) = tcx.intrinsic(def_id)
             {
-                match intrinsic_name {
+                match intrinsic.name {
                     sym::unreachable => {
                         terminator.kind = TerminatorKind::Unreachable;
                     }
@@ -105,7 +105,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
                             lhs = args.next().unwrap();
                             rhs = args.next().unwrap();
                         }
-                        let bin_op = match intrinsic_name {
+                        let bin_op = match intrinsic.name {
                             sym::wrapping_add => BinOp::Add,
                             sym::wrapping_sub => BinOp::Sub,
                             sym::wrapping_mul => BinOp::Mul,
@@ -136,7 +136,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
                                 lhs = args.next().unwrap();
                                 rhs = args.next().unwrap();
                             }
-                            let bin_op = match intrinsic_name {
+                            let bin_op = match intrinsic.name {
                                 sym::add_with_overflow => BinOp::Add,
                                 sym::sub_with_overflow => BinOp::Sub,
                                 sym::mul_with_overflow => BinOp::Mul,
@@ -155,7 +155,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
                     sym::size_of | sym::min_align_of => {
                         if let Some(target) = *target {
                             let tp_ty = generic_args.type_at(0);
-                            let null_op = match intrinsic_name {
+                            let null_op = match intrinsic.name {
                                 sym::size_of => NullOp::SizeOf,
                                 sym::min_align_of => NullOp::AlignOf,
                                 _ => bug!("unexpected intrinsic"),
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 5593de60784..b0cb9fa517f 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -1019,6 +1019,11 @@ fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) ->
         return false;
     }
 
+    if tcx.intrinsic(def_id).is_some_and(|i| i.must_be_overridden) {
+        // These are implemented by backends directly and have no meaningful body.
+        return false;
+    }
+
     if def_id.is_local() {
         // Local items cannot be referred to locally without monomorphizing them locally.
         return true;
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 7e31cfc0662..73088d2cf7b 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1525,6 +1525,7 @@ symbols! {
         rustc_inherit_overflow_checks,
         rustc_insignificant_dtor,
         rustc_intrinsic,
+        rustc_intrinsic_must_be_overridden,
         rustc_layout,
         rustc_layout_scalar_valid_range_end,
         rustc_layout_scalar_valid_range_start,
diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs
index 3e3bccce47f..381681fb1f4 100644
--- a/compiler/rustc_ty_utils/src/instance.rs
+++ b/compiler/rustc_ty_utils/src/instance.rs
@@ -1,5 +1,4 @@
 use rustc_errors::ErrorGuaranteed;
-use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_middle::query::Providers;
@@ -28,8 +27,7 @@ fn resolve_instance<'tcx>(
             tcx.normalize_erasing_regions(param_env, args),
         )
     } else {
-        let def = if matches!(tcx.def_kind(def_id), DefKind::Fn) && tcx.intrinsic(def_id).is_some()
-        {
+        let def = if tcx.intrinsic(def_id).is_some() {
             debug!(" => intrinsic");
             ty::InstanceDef::Intrinsic(def_id)
         } else if Some(def_id) == tcx.lang_items().drop_in_place_fn() {
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index 96e667d63c5..fd9dc4c46bd 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -2499,9 +2499,8 @@ extern "rust-intrinsic" {
     #[rustc_nounwind]
     pub fn black_box<T>(dummy: T) -> T;
 
-    /// `ptr` must point to a vtable.
-    /// The intrinsic will return the size stored in that vtable.
     #[rustc_nounwind]
+    #[cfg(bootstrap)]
     pub fn vtable_size(ptr: *const ()) -> usize;
 
     /// `ptr` must point to a vtable.
@@ -2681,6 +2680,17 @@ pub const unsafe fn const_allocate(_size: usize, _align: usize) -> *mut u8 {
 #[cfg_attr(bootstrap, inline)]
 pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {}
 
+/// `ptr` must point to a vtable.
+/// The intrinsic will return the size stored in that vtable.
+#[rustc_nounwind]
+#[unstable(feature = "core_intrinsics", issue = "none")]
+#[cfg_attr(not(bootstrap), rustc_intrinsic)]
+#[cfg_attr(not(bootstrap), rustc_intrinsic_must_be_overridden)]
+#[cfg(not(bootstrap))]
+pub unsafe fn vtable_size(_ptr: *const ()) -> usize {
+    unreachable!()
+}
+
 // Some functions are defined here because they accidentally got made
 // available in this module on stable. See <https://github.com/rust-lang/rust/issues/15702>.
 // (`transmute` also falls into this category, but it cannot be wrapped due to the
diff --git a/src/doc/unstable-book/src/language-features/intrinsics.md b/src/doc/unstable-book/src/language-features/intrinsics.md
index 1a8c1c0b36a..02a009d56d3 100644
--- a/src/doc/unstable-book/src/language-features/intrinsics.md
+++ b/src/doc/unstable-book/src/language-features/intrinsics.md
@@ -52,12 +52,23 @@ with any regular function.
 Various intrinsics have native MIR operations that they correspond to. Instead of requiring
 backends to implement both the intrinsic and the MIR operation, the `lower_intrinsics` pass
 will convert the calls to the MIR operation. Backends do not need to know about these intrinsics
-at all.
+at all. These intrinsics only make sense without a body, and can either be declared as a "rust-intrinsic"
+or as a `#[rustc_intrinsic]`. The body is never used, as calls to the intrinsic do not exist
+anymore after MIR analyses.
 
 ## Intrinsics without fallback logic
 
 These must be implemented by all backends.
 
+### `#[rustc_intrinsic]` declarations
+
+These are written like intrinsics with fallback bodies, but the body is irrelevant.
+Use `loop {}` for the body or call the intrinsic recursively and add
+`#[rustc_intrinsic_must_be_overridden]` to the function to ensure that backends don't
+invoke the body.
+
+### Legacy extern ABI based intrinsics
+
 These are imported as if they were FFI functions, with the special
 `rust-intrinsic` ABI. For example, if one was in a freestanding
 context, but wished to be able to `transmute` between types, and
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index 183dbe3aecc..e369cb9d0a4 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -334,7 +334,7 @@ fn check_terminator<'tcx>(
                 // within const fns. `transmute` is allowed in all other const contexts.
                 // This won't really scale to more intrinsics or functions. Let's allow const
                 // transmutes in const fn before we add more hacks to this.
-                if matches!(tcx.intrinsic(fn_def_id), Some(sym::transmute)) {
+                if tcx.is_intrinsic(fn_def_id, sym::transmute) {
                     return Err((
                         span,
                         "can only call `transmute` from const items, not `const fn`".into(),
diff --git a/tests/ui/intrinsics/always-gets-overridden.rs b/tests/ui/intrinsics/always-gets-overridden.rs
new file mode 100644
index 00000000000..ad2c2be4daa
--- /dev/null
+++ b/tests/ui/intrinsics/always-gets-overridden.rs
@@ -0,0 +1,20 @@
+//! Check that `vtable_size` gets overridden by llvm backend even if there is no
+//! `rustc_intrinsic_must_be_overridden` attribute on this usage.
+#![feature(rustc_attrs)]
+//@run-pass
+
+#[rustc_intrinsic]
+pub unsafe fn vtable_size(_ptr: *const ()) -> usize {
+    panic!();
+}
+
+trait Trait {}
+impl Trait for () {}
+
+fn main() {
+    let x: &dyn Trait = &();
+    unsafe {
+        let (_data, vtable): (*const (), *const ()) = core::mem::transmute(x);
+        assert_eq!(vtable_size(vtable), 0);
+    }
+}
diff --git a/tests/ui/intrinsics/not-overridden.rs b/tests/ui/intrinsics/not-overridden.rs
new file mode 100644
index 00000000000..d6655b51905
--- /dev/null
+++ b/tests/ui/intrinsics/not-overridden.rs
@@ -0,0 +1,18 @@
+//! Check that intrinsics that do not get overridden, but are marked as such,
+//! cause an error instead of silently invoking the body.
+#![feature(rustc_attrs, effects)]
+//@ build-fail
+//@ failure-status:101
+//@ normalize-stderr-test ".*note: .*\n\n" -> ""
+//@ normalize-stderr-test "thread 'rustc' panicked.*:\n.*\n" -> ""
+//@ normalize-stderr-test "internal compiler error:.*: intrinsic const_deallocate " -> ""
+//@ rustc-env:RUST_BACKTRACE=0
+
+#[rustc_intrinsic]
+#[rustc_intrinsic_must_be_overridden]
+pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {}
+
+fn main() {
+    unsafe { const_deallocate(std::ptr::null_mut(), 0, 0) }
+    //~^ ERROR: must be overridden
+}
diff --git a/tests/ui/intrinsics/not-overridden.stderr b/tests/ui/intrinsics/not-overridden.stderr
new file mode 100644
index 00000000000..9b8849cea1c
--- /dev/null
+++ b/tests/ui/intrinsics/not-overridden.stderr
@@ -0,0 +1,10 @@
+error: must be overridden by codegen backend, but isn't
+  --> $DIR/not-overridden.rs:16:14
+   |
+LL |     unsafe { const_deallocate(std::ptr::null_mut(), 0, 0) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+query stack during panic:
+end of query stack
+error: aborting due to 1 previous error
+