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
This commit is contained in:
bors 2024-03-05 00:13:01 +00:00
commit 2eeff462b7
31 changed files with 207 additions and 51 deletions

View File

@ -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: // (Eventually this should use const-generics, but those are not up for the task yet:
// https://github.com/rust-lang/rust/issues/85229.) // https://github.com/rust-lang/rust/issues/85229.)
if let Some(name @ (sym::simd_shuffle | sym::simd_insert | sym::simd_extract)) = 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 { let idx = match name {
sym::simd_shuffle => 2, sym::simd_shuffle => 2,

View File

@ -1255,7 +1255,17 @@ fn codegen_regular_intrinsic_call<'tcx>(
// Unimplemented intrinsics must have a fallback body. The fallback body is obtained // Unimplemented intrinsics must have a fallback body. The fallback body is obtained
// by converting the `InstanceDef::Intrinsic` to an `InstanceDef::Item`. // 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()); let ret_block = fx.get_block(destination.unwrap());

View File

@ -81,6 +81,10 @@ fn reachable_non_generics_provider(tcx: TyCtxt<'_>, _: LocalCrate) -> DefIdMap<S
return library.kind.is_statically_included().then_some(def_id); 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. // Only consider nodes that actually have exported symbols.
match tcx.def_kind(def_id) { match tcx.def_kind(def_id) {
DefKind::Fn | DefKind::Static(_) => {} DefKind::Fn | DefKind::Static(_) => {}

View File

@ -12,12 +12,12 @@ use crate::MemFlags;
use rustc_ast as ast; use rustc_ast as ast;
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
use rustc_hir::lang_items::LangItem; 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::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::ty::{self, Instance, Ty};
use rustc_session::config::OptLevel; 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::call::{ArgAbi, FnAbi, PassMode, Reg};
use rustc_target::abi::{self, HasDataLayout, WrappingRange}; use rustc_target::abi::{self, HasDataLayout, WrappingRange};
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
@ -680,7 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
&mut self, &mut self,
helper: &TerminatorCodegenHelper<'tcx>, helper: &TerminatorCodegenHelper<'tcx>,
bx: &mut Bx, bx: &mut Bx,
intrinsic: Option<Symbol>, intrinsic: Option<ty::IntrinsicDef>,
instance: Option<Instance<'tcx>>, instance: Option<Instance<'tcx>>,
source_info: mir::SourceInfo, source_info: mir::SourceInfo,
target: Option<mir::BasicBlock>, 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. // Emit a panic or a no-op for `assert_*` intrinsics.
// These are intrinsics that compile to panics so that we can get a message // These are intrinsics that compile to panics so that we can get a message
// which mentions the offending type, even from a const context. // 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 { if let Some(requirement) = panic_intrinsic {
let ty = instance.unwrap().args.type_at(0); 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. // 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; 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 { return if let Some(target) = target {
let location = let location =
self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info }); self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info });
let mut llargs = Vec::with_capacity(arg_count); let mut llargs = Vec::with_capacity(arg_count);
let ret_dest = let ret_dest = self.make_return_dest(
self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs, true, true); bx,
destination,
&fn_abi.ret,
&mut llargs,
intrinsic,
Some(target),
);
assert_eq!(llargs, []); assert_eq!(llargs, []);
if let ReturnDest::IndirectOperand(tmp, _) = ret_dest { if let ReturnDest::IndirectOperand(tmp, _) = ret_dest {
location.val.store(bx, tmp); location.val.store(bx, tmp);
@ -846,7 +852,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
} }
let instance = match intrinsic { let instance = match intrinsic {
None | Some(sym::drop_in_place) => instance, None | Some(ty::IntrinsicDef { name: sym::drop_in_place, .. }) => instance,
Some(intrinsic) => { Some(intrinsic) => {
let mut llargs = Vec::with_capacity(1); let mut llargs = Vec::with_capacity(1);
let ret_dest = self.make_return_dest( let ret_dest = self.make_return_dest(
@ -854,8 +860,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
destination, destination,
&fn_abi.ret, &fn_abi.ret,
&mut llargs, &mut llargs,
true, Some(intrinsic),
target.is_some(), target,
); );
let dest = match ret_dest { let dest = match ret_dest {
_ if fn_abi.ret.is_indirect() => llargs[0], _ 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 // The indices passed to simd_shuffle in the
// third argument must be constant. This is // third argument must be constant. This is
// checked by the type-checker. // 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 { if let mir::Operand::Constant(constant) = &arg.node {
let (llval, ty) = self.simd_shuffle_indices(bx, constant); let (llval, ty) = self.simd_shuffle_indices(bx, constant);
return OperandRef { return OperandRef {
@ -903,14 +909,33 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
MergingSucc::False 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 mut llargs = Vec::with_capacity(arg_count);
let destination = target.as_ref().map(|&target| { 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. // 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>, dest: mir::Place<'tcx>,
fn_ret: &ArgAbi<'tcx, Ty<'tcx>>, fn_ret: &ArgAbi<'tcx, Ty<'tcx>>,
llargs: &mut Vec<Bx::Value>, llargs: &mut Vec<Bx::Value>,
is_intrinsic: bool, intrinsic: Option<ty::IntrinsicDef>,
has_target: bool, target: Option<BasicBlock>,
) -> ReturnDest<'tcx, Bx::Value> { ) -> ReturnDest<'tcx, Bx::Value> {
if !has_target { if target.is_none() {
return ReturnDest::Nothing; return ReturnDest::Nothing;
} }
// If the return is ignored, we can just return a do-nothing `ReturnDest`. // 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); tmp.storage_live(bx);
llargs.push(tmp.llval); llargs.push(tmp.llval);
ReturnDest::IndirectOperand(tmp, index) ReturnDest::IndirectOperand(tmp, index)
} else if is_intrinsic { } else if intrinsic.is_some() {
// Currently, intrinsics always need a location to store // Currently, intrinsics always need a location to store
// the result, so we create a temporary `alloca` for the // the result, so we create a temporary `alloca` for the
// result. // result.

View File

@ -867,6 +867,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_no_mir_inline, Normal, template!(Word), WarnFollowing, 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_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: // Internal attributes, Testing:

View File

@ -527,12 +527,12 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
check_enum(tcx, def_id); check_enum(tcx, def_id);
} }
DefKind::Fn => { DefKind::Fn => {
if let Some(name) = tcx.intrinsic(def_id) { if let Some(i) = tcx.intrinsic(def_id) {
intrinsic::check_intrinsic_type( intrinsic::check_intrinsic_type(
tcx, tcx,
def_id, def_id,
tcx.def_ident_span(def_id).unwrap(), tcx.def_ident_span(def_id).unwrap(),
name, i.name,
Abi::Rust, Abi::Rust,
) )
} }

View File

@ -545,7 +545,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(def_id) = def_id if let Some(def_id) = def_id
&& self.tcx.def_kind(def_id) == hir::def::DefKind::Fn && 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); let fn_sig = self.resolve_vars_if_possible(fn_sig);
for idx in 0..=1 { for idx in 0..=1 {

View File

@ -1231,7 +1231,7 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
} }
fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { 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)
} }
} }
} }

View File

@ -1749,7 +1749,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
self.root.tables.attr_flags.get(self, index) 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)) self.root.tables.intrinsic.get(self, index).map(|d| d.decode(self))
} }

View File

@ -1053,11 +1053,14 @@ fn should_encode_mir(
// Full-fledged functions + closures // Full-fledged functions + closures
DefKind::AssocFn | DefKind::Fn | DefKind::Closure => { DefKind::AssocFn | DefKind::Fn | DefKind::Closure => {
let generics = tcx.generics_of(def_id); 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() || (tcx.sess.opts.output_types.should_codegen()
&& reachable_set.contains(&def_id) && reachable_set.contains(&def_id)
&& (generics.requires_monomorphization(tcx) && (generics.requires_monomorphization(tcx)
|| tcx.cross_crate_inlinable(def_id))); || 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]`. // 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()) let is_const_fn = tcx.is_const_fn_raw(def_id.to_def_id())
|| tcx.is_const_default_method(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 { if let DefKind::Fn | DefKind::AssocFn = def_kind {
self.tables.asyncness.set_some(def_id.index, tcx.asyncness(def_id)); 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)); 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 { if let DefKind::TyParam = def_kind {
let default = self.tcx.object_lifetime_default(def_id); let default = self.tcx.object_lifetime_default(def_id);

View File

@ -375,7 +375,7 @@ macro_rules! define_tables {
define_tables! { define_tables! {
- defaulted: - defaulted:
intrinsic: Table<DefIndex, Option<LazyValue<Symbol>>>, intrinsic: Table<DefIndex, Option<LazyValue<ty::IntrinsicDef>>>,
is_macro_rules: Table<DefIndex, bool>, is_macro_rules: Table<DefIndex, bool>,
is_type_alias_impl_trait: Table<DefIndex, bool>, is_type_alias_impl_trait: Table<DefIndex, bool>,
type_alias_is_lazy: Table<DefIndex, bool>, type_alias_is_lazy: Table<DefIndex, bool>,

View File

@ -241,7 +241,7 @@ trivial! {
Option<rustc_target::abi::FieldIdx>, Option<rustc_target::abi::FieldIdx>,
Option<rustc_target::spec::PanicStrategy>, Option<rustc_target::spec::PanicStrategy>,
Option<usize>, Option<usize>,
Option<rustc_span::Symbol>, Option<rustc_middle::ty::IntrinsicDef>,
Result<(), rustc_errors::ErrorGuaranteed>, Result<(), rustc_errors::ErrorGuaranteed>,
Result<(), rustc_middle::traits::query::NoSolution>, Result<(), rustc_middle::traits::query::NoSolution>,
Result<rustc_middle::traits::EvaluationResult, rustc_middle::traits::OverflowError>, Result<rustc_middle::traits::EvaluationResult, rustc_middle::traits::OverflowError>,

View File

@ -1760,7 +1760,7 @@ rustc_queries! {
separate_provide_extern separate_provide_extern
} }
/// Whether the function is an intrinsic /// 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) } desc { |tcx| "fetch intrinsic name if `{}` is an intrinsic", tcx.def_path_str(def_id) }
separate_provide_extern separate_provide_extern
} }

View File

@ -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
}
}

View File

@ -30,6 +30,7 @@ pub use adt::*;
pub use assoc::*; pub use assoc::*;
pub use generic_args::*; pub use generic_args::*;
pub use generics::*; pub use generics::*;
pub use intrinsic::IntrinsicDef;
use rustc_ast as ast; use rustc_ast as ast;
use rustc_ast::node_id::NodeMap; use rustc_ast::node_id::NodeMap;
pub use rustc_ast_ir::{Movability, Mutability}; pub use rustc_ast_ir::{Movability, Mutability};
@ -149,6 +150,7 @@ mod generic_args;
mod generics; mod generics;
mod impls_ty; mod impls_ty;
mod instance; mod instance;
mod intrinsic;
mod list; mod list;
mod opaque_types; mod opaque_types;
mod parameterized; mod parameterized;

View File

@ -75,6 +75,7 @@ trivially_parameterized_over_tcx! {
ty::Visibility<DefIndex>, ty::Visibility<DefIndex>,
ty::adjustment::CoerceUnsizedInfo, ty::adjustment::CoerceUnsizedInfo,
ty::fast_reject::SimplifiedType, ty::fast_reject::SimplifiedType,
ty::IntrinsicDef,
rustc_ast::Attribute, rustc_ast::Attribute,
rustc_ast::DelimArgs, rustc_ast::DelimArgs,
rustc_ast::expand::StrippedCfgItem<rustc_hir::def_id::DefIndex>, rustc_ast::expand::StrippedCfgItem<rustc_hir::def_id::DefIndex>,

View File

@ -19,7 +19,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
use rustc_index::bit_set::GrowableBitSet; use rustc_index::bit_set::GrowableBitSet;
use rustc_macros::HashStable; use rustc_macros::HashStable;
use rustc_session::Limit; use rustc_session::Limit;
use rustc_span::{sym, Symbol}; use rustc_span::sym;
use rustc_target::abi::{Integer, IntegerType, Primitive, Size}; use rustc_target::abi::{Integer, IntegerType, Primitive, Size};
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use smallvec::SmallVec; 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))) .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 /// 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<Symbol> { 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) if matches!(tcx.fn_sig(def_id).skip_binder().abi(), Abi::RustIntrinsic)
|| tcx.has_attr(def_id, sym::rustc_intrinsic) || 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 { } else {
None None
} }

View File

@ -202,7 +202,7 @@ impl PeekCall {
&terminator.kind &terminator.kind
{ {
if let ty::FnDef(def_id, fn_args) = *func.const_.ty().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; return None;
} }

View File

@ -23,6 +23,10 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
return false; 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. // This just reproduces the logic from Instance::requires_inline.
match tcx.def_kind(def_id) { match tcx.def_kind(def_id) {
DefKind::Ctor(..) | DefKind::Closure => return true, DefKind::Ctor(..) | DefKind::Closure => return true,

View File

@ -323,8 +323,8 @@ fn resolve_rust_intrinsic<'tcx>(
func_ty: Ty<'tcx>, func_ty: Ty<'tcx>,
) -> Option<(Symbol, GenericArgsRef<'tcx>)> { ) -> Option<(Symbol, GenericArgsRef<'tcx>)> {
if let ty::FnDef(def_id, args) = *func_ty.kind() { if let ty::FnDef(def_id, args) = *func_ty.kind() {
let name = tcx.intrinsic(def_id)?; let intrinsic = tcx.intrinsic(def_id)?;
return Some((name, args)); return Some((intrinsic.name, args));
} }
None None
} }

View File

@ -160,7 +160,7 @@ fn remap_mir_for_const_eval_select<'tcx>(
fn_span, fn_span,
.. ..
} if let ty::FnDef(def_id, _) = *const_.ty().kind() } 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] = let [tupled_args, called_in_const, called_at_rt]: [_; 3] =
std::mem::take(args).try_into().unwrap(); 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<'_> { 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()) { if tcx.is_constructor(did.to_def_id()) {
// There's no reason to run all of the MIR passes on constructors when // 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 // we can just output the MIR we want directly. This also saves const

View File

@ -14,9 +14,9 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
if let TerminatorKind::Call { func, args, destination, target, .. } = if let TerminatorKind::Call { func, args, destination, target, .. } =
&mut terminator.kind &mut terminator.kind
&& let ty::FnDef(def_id, generic_args) = *func.ty(local_decls, tcx).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 => { sym::unreachable => {
terminator.kind = TerminatorKind::Unreachable; terminator.kind = TerminatorKind::Unreachable;
} }
@ -105,7 +105,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
lhs = args.next().unwrap(); lhs = args.next().unwrap();
rhs = 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_add => BinOp::Add,
sym::wrapping_sub => BinOp::Sub, sym::wrapping_sub => BinOp::Sub,
sym::wrapping_mul => BinOp::Mul, sym::wrapping_mul => BinOp::Mul,
@ -136,7 +136,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
lhs = args.next().unwrap(); lhs = args.next().unwrap();
rhs = 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::add_with_overflow => BinOp::Add,
sym::sub_with_overflow => BinOp::Sub, sym::sub_with_overflow => BinOp::Sub,
sym::mul_with_overflow => BinOp::Mul, sym::mul_with_overflow => BinOp::Mul,
@ -155,7 +155,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
sym::size_of | sym::min_align_of => { sym::size_of | sym::min_align_of => {
if let Some(target) = *target { if let Some(target) = *target {
let tp_ty = generic_args.type_at(0); 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::size_of => NullOp::SizeOf,
sym::min_align_of => NullOp::AlignOf, sym::min_align_of => NullOp::AlignOf,
_ => bug!("unexpected intrinsic"), _ => bug!("unexpected intrinsic"),

View File

@ -1019,6 +1019,11 @@ fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) ->
return false; 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() { if def_id.is_local() {
// Local items cannot be referred to locally without monomorphizing them locally. // Local items cannot be referred to locally without monomorphizing them locally.
return true; return true;

View File

@ -1525,6 +1525,7 @@ symbols! {
rustc_inherit_overflow_checks, rustc_inherit_overflow_checks,
rustc_insignificant_dtor, rustc_insignificant_dtor,
rustc_intrinsic, rustc_intrinsic,
rustc_intrinsic_must_be_overridden,
rustc_layout, rustc_layout,
rustc_layout_scalar_valid_range_end, rustc_layout_scalar_valid_range_end,
rustc_layout_scalar_valid_range_start, rustc_layout_scalar_valid_range_start,

View File

@ -1,5 +1,4 @@
use rustc_errors::ErrorGuaranteed; use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::query::Providers; use rustc_middle::query::Providers;
@ -28,8 +27,7 @@ fn resolve_instance<'tcx>(
tcx.normalize_erasing_regions(param_env, args), tcx.normalize_erasing_regions(param_env, args),
) )
} else { } 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"); debug!(" => intrinsic");
ty::InstanceDef::Intrinsic(def_id) ty::InstanceDef::Intrinsic(def_id)
} else if Some(def_id) == tcx.lang_items().drop_in_place_fn() { } else if Some(def_id) == tcx.lang_items().drop_in_place_fn() {

View File

@ -2499,9 +2499,8 @@ extern "rust-intrinsic" {
#[rustc_nounwind] #[rustc_nounwind]
pub fn black_box<T>(dummy: T) -> T; 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] #[rustc_nounwind]
#[cfg(bootstrap)]
pub fn vtable_size(ptr: *const ()) -> usize; pub fn vtable_size(ptr: *const ()) -> usize;
/// `ptr` must point to a vtable. /// `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)] #[cfg_attr(bootstrap, inline)]
pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {} 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 // 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>. // 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 // (`transmute` also falls into this category, but it cannot be wrapped due to the

View File

@ -52,12 +52,23 @@ with any regular function.
Various intrinsics have native MIR operations that they correspond to. Instead of requiring 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 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 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 ## Intrinsics without fallback logic
These must be implemented by all backends. 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 These are imported as if they were FFI functions, with the special
`rust-intrinsic` ABI. For example, if one was in a freestanding `rust-intrinsic` ABI. For example, if one was in a freestanding
context, but wished to be able to `transmute` between types, and context, but wished to be able to `transmute` between types, and

View File

@ -334,7 +334,7 @@ fn check_terminator<'tcx>(
// within const fns. `transmute` is allowed in all other const contexts. // 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 // 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. // 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(( return Err((
span, span,
"can only call `transmute` from const items, not `const fn`".into(), "can only call `transmute` from const items, not `const fn`".into(),

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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