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 {} 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, + intrinsic: Option, instance: Option>, source_info: mir::SourceInfo, target: Option, @@ -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, - is_intrinsic: bool, - has_target: bool, + intrinsic: Option, + target: Option, ) -> 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 { + fn get_intrinsic(self, index: DefIndex) -> Option { 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>>, + intrinsic: Table>>, is_macro_rules: Table, is_type_alias_impl_trait: Table, type_alias_is_lazy: Table, 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, Option, Option, - Option, + Option, Result<(), rustc_errors::ErrorGuaranteed>, Result<(), rustc_middle::traits::query::NoSolution>, Result, 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 { + query intrinsic(def_id: DefId) -> Option { 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, ty::adjustment::CoerceUnsizedInfo, ty::fast_reject::SimplifiedType, + ty::IntrinsicDef, rustc_ast::Attribute, rustc_ast::DelimArgs, rustc_ast::expand::StrippedCfgItem, 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 { +/// 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 { + 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(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 . // (`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 +