compiler: Add is_uninhabited and use LayoutS accessors

This reduces the need of the compiler to peek on the fields of LayoutS.
This commit is contained in:
Jubilee Young 2024-10-27 21:34:49 -07:00
parent 5f5c243ca0
commit 88a9edc091
20 changed files with 40 additions and 37 deletions

View File

@ -4095,6 +4095,7 @@ version = "0.0.0"
dependencies = [
"either",
"itertools",
"rustc_abi",
"rustc_arena",
"rustc_ast",
"rustc_attr",

View File

@ -28,7 +28,7 @@ where
VariantIdx: Idx,
F: Deref<Target = &'a LayoutS<FieldIdx, VariantIdx>> + fmt::Debug,
{
let uninhabited = fields.iter().any(|f| f.abi.is_uninhabited());
let uninhabited = fields.iter().any(|f| f.is_uninhabited());
// We cannot ignore alignment; that might lead us to entirely discard a variant and
// produce an enum that is less aligned than it should be!
let is_1zst = fields.iter().all(|f| f.is_1zst());
@ -681,7 +681,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
let discr_type = repr.discr_type();
let bits = Integer::from_attr(dl, discr_type).size().bits();
for (i, mut val) in discriminants {
if !repr.c() && variants[i].iter().any(|f| f.abi.is_uninhabited()) {
if !repr.c() && variants[i].iter().any(|f| f.is_uninhabited()) {
continue;
}
if discr_type.is_signed() {

View File

@ -1652,6 +1652,11 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutS<FieldIdx, VariantIdx> {
}
}
/// Returns `true` if this is an uninhabited type
pub fn is_uninhabited(&self) -> bool {
self.abi.is_uninhabited()
}
pub fn scalar<C: HasDataLayout>(cx: &C, scalar: Scalar) -> Self {
let largest_niche = Niche::from_scalar(cx, Size::ZERO, scalar);
let size = scalar.size(cx);

View File

@ -415,7 +415,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
instance: Option<ty::Instance<'tcx>>,
) {
let mut func_attrs = SmallVec::<[_; 3]>::new();
if self.ret.layout.abi.is_uninhabited() {
if self.ret.layout.is_uninhabited() {
func_attrs.push(llvm::AttributeKind::NoReturn.create_attr(cx.llcx));
}
if !self.can_unwind {
@ -532,7 +532,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
fn apply_attrs_callsite(&self, bx: &mut Builder<'_, 'll, 'tcx>, callsite: &'ll Value) {
let mut func_attrs = SmallVec::<[_; 2]>::new();
if self.ret.layout.abi.is_uninhabited() {
if self.ret.layout.is_uninhabited() {
func_attrs.push(llvm::AttributeKind::NoReturn.create_attr(bx.cx.llcx));
}
if !self.can_unwind {

View File

@ -364,7 +364,7 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
let mut flags = DIFlags::FlagPrototyped;
if fn_abi.ret.layout.abi.is_uninhabited() {
if fn_abi.ret.layout.is_uninhabited() {
flags |= DIFlags::FlagNoReturn;
}

View File

@ -438,7 +438,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
_ => bug!("C-variadic function must have a `VaList` place"),
}
}
if self.fn_abi.ret.layout.abi.is_uninhabited() {
if self.fn_abi.ret.layout.is_uninhabited() {
// Functions with uninhabited return values are marked `noreturn`,
// so we should make sure that we never actually do.
// We play it safe by using a well-defined `abort`, but we could go for immediate UB
@ -774,7 +774,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
Some(if do_panic {
let msg_str = with_no_visible_paths!({
with_no_trimmed_paths!({
if layout.abi.is_uninhabited() {
if layout.is_uninhabited() {
// Use this error even for the other intrinsics as it is more precise.
format!("attempted to instantiate uninhabited type `{ty}`")
} else if requirement == ValidityRequirement::Zero {

View File

@ -55,7 +55,7 @@ impl<V: CodegenObject> PlaceValue<V> {
/// Creates a `PlaceRef` to this location with the given type.
pub fn with_type<'tcx>(self, layout: TyAndLayout<'tcx>) -> PlaceRef<'tcx, V> {
assert!(
layout.is_unsized() || layout.abi.is_uninhabited() || self.llextra.is_none(),
layout.is_unsized() || layout.is_uninhabited() || self.llextra.is_none(),
"Had pointer metadata {:?} for sized type {layout:?}",
self.llextra,
);
@ -239,7 +239,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
let dl = &bx.tcx().data_layout;
let cast_to_layout = bx.cx().layout_of(cast_to);
let cast_to = bx.cx().immediate_backend_type(cast_to_layout);
if self.layout.abi.is_uninhabited() {
if self.layout.is_uninhabited() {
return bx.cx().const_poison(cast_to);
}
let (tag_scalar, tag_encoding, tag_field) = match self.layout.variants {
@ -358,7 +358,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
bx: &mut Bx,
variant_index: VariantIdx,
) {
if self.layout.for_variant(bx.cx(), variant_index).abi.is_uninhabited() {
if self.layout.for_variant(bx.cx(), variant_index).is_uninhabited() {
// We play it safe by using a well-defined `abort`, but we could go for immediate UB
// if that turns out to be helpful.
bx.abort();

View File

@ -203,10 +203,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
) -> Option<OperandValue<Bx::Value>> {
// Check for transmutes that are always UB.
if operand.layout.size != cast.size
|| operand.layout.abi.is_uninhabited()
|| cast.abi.is_uninhabited()
|| operand.layout.is_uninhabited()
|| cast.is_uninhabited()
{
if !operand.layout.abi.is_uninhabited() {
if !operand.layout.is_uninhabited() {
// Since this is known statically and the input could have existed
// without already having hit UB, might as well trap for it.
bx.abort();
@ -555,7 +555,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
assert!(bx.cx().is_backend_immediate(cast));
let to_backend_ty = bx.cx().immediate_backend_type(cast);
if operand.layout.abi.is_uninhabited() {
if operand.layout.is_uninhabited() {
let val = OperandValue::Immediate(bx.cx().const_poison(to_backend_ty));
return OperandRef { val, layout: cast };
}

View File

@ -395,7 +395,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
#[inline(always)]
fn enforce_validity(ecx: &InterpCx<'tcx, Self>, layout: TyAndLayout<'tcx>) -> bool {
ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks || layout.abi.is_uninhabited()
ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks || layout.is_uninhabited()
}
fn load_mir(

View File

@ -27,7 +27,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// discriminant, so we cannot do anything here.
// When evaluating we will always error before even getting here, but ConstProp 'executes'
// dead code, so we cannot ICE here.
if dest.layout().for_variant(self, variant_index).abi.is_uninhabited() {
if dest.layout().for_variant(self, variant_index).is_uninhabited() {
throw_ub!(UninhabitedEnumVariantWritten(variant_index))
}
@ -86,7 +86,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// For consistency with `write_discriminant`, and to make sure that
// `project_downcast` cannot fail due to strange layouts, we declare immediate UB
// for uninhabited variants.
if op.layout().for_variant(self, index).abi.is_uninhabited() {
if op.layout().for_variant(self, index).is_uninhabited() {
throw_ub!(UninhabitedEnumVariantRead(index))
}
}
@ -203,7 +203,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// Reading the discriminant of an uninhabited variant is UB. This is the basis for the
// `uninhabited_enum_branching` MIR pass. It also ensures consistency with
// `write_discriminant`.
if op.layout().for_variant(self, index).abi.is_uninhabited() {
if op.layout().for_variant(self, index).is_uninhabited() {
throw_ub!(UninhabitedEnumVariantRead(index))
}
interp_ok(index)

View File

@ -364,7 +364,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let msg = match requirement {
// For *all* intrinsics we first check `is_uninhabited` to give a more specific
// error message.
_ if layout.abi.is_uninhabited() => format!(
_ if layout.is_uninhabited() => format!(
"aborted execution: attempted to instantiate uninhabited type `{ty}`"
),
ValidityRequirement::Inhabited => bug!("handled earlier"),

View File

@ -315,7 +315,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let ptr = left.to_scalar().to_pointer(self)?;
let pointee_ty = left.layout.ty.builtin_deref(true).unwrap();
let pointee_layout = self.layout_of(pointee_ty)?;
assert!(pointee_layout.abi.is_sized());
assert!(pointee_layout.is_sized());
// The size always fits in `i64` as it can be at most `isize::MAX`.
let pointee_size = i64::try_from(pointee_layout.size.bytes()).unwrap();
@ -518,14 +518,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
interp_ok(match null_op {
SizeOf => {
if !layout.abi.is_sized() {
if !layout.is_sized() {
span_bug!(self.cur_span(), "unsized type for `NullaryOp::SizeOf`");
}
let val = layout.size.bytes();
ImmTy::from_uint(val, usize_layout())
}
AlignOf => {
if !layout.abi.is_sized() {
if !layout.is_sized() {
span_bug!(self.cur_span(), "unsized type for `NullaryOp::AlignOf`");
}
let val = layout.align.abi.bytes();

View File

@ -542,7 +542,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
throw_validation_failure!(self.path, NullPtr { ptr_kind })
}
// Do not allow references to uninhabited types.
if place.layout.abi.is_uninhabited() {
if place.layout.is_uninhabited() {
let ty = place.layout.ty;
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
}
@ -867,7 +867,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
/// Add the entire given place to the "data" range of this visit.
fn add_data_range_place(&mut self, place: &PlaceTy<'tcx, M::Provenance>) {
// Only sized places can be added this way.
debug_assert!(place.layout.abi.is_sized());
debug_assert!(place.layout.is_sized());
if let Some(data_bytes) = self.data_bytes.as_mut() {
let offset = Self::data_range_offset(self.ecx, place);
data_bytes.add_range(offset, place.layout.size);
@ -945,7 +945,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
layout: TyAndLayout<'tcx>,
) -> Cow<'e, RangeSet> {
assert!(layout.ty.is_union());
assert!(layout.abi.is_sized(), "there are no unsized unions");
assert!(layout.is_sized(), "there are no unsized unions");
let layout_cx = LayoutCx::new(*ecx.tcx, ecx.param_env);
return M::cached_union_data_range(ecx, layout.ty, || {
let mut out = RangeSet(Vec::new());

View File

@ -29,7 +29,7 @@ pub fn check_validity_requirement<'tcx>(
// There is nothing strict or lax about inhabitedness.
if kind == ValidityRequirement::Inhabited {
return Ok(!layout.abi.is_uninhabited());
return Ok(!layout.is_uninhabited());
}
let layout_cx = LayoutCx::new(tcx, param_env_and_ty.param_env);

View File

@ -172,7 +172,7 @@ fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) {
return;
}
};
if layout.abi.is_uninhabited() {
if layout.is_uninhabited() {
tcx.node_span_lint(
UNINHABITED_STATIC,
tcx.local_def_id_to_hir_id(def_id),

View File

@ -346,7 +346,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
// First try computing a static layout.
let err = match tcx.layout_of(param_env.and(ty)) {
Ok(layout) => {
if layout.abi.is_sized() {
if layout.is_sized() {
return Ok(SizeSkeleton::Known(layout.size, Some(layout.align.abi)));
} else {
// Just to be safe, don't claim a known layout for unsized types.

View File

@ -7,6 +7,7 @@ edition = "2021"
# tidy-alphabetical-start
either = "1"
itertools = "0.12"
rustc_abi = { path = "../rustc_abi" }
rustc_arena = { path = "../rustc_arena" }
rustc_ast = { path = "../rustc_ast" }
rustc_attr = { path = "../rustc_attr" }

View File

@ -1,5 +1,6 @@
//! A pass that eliminates branches on uninhabited or unreachable enum variants.
use rustc_abi::Variants;
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::bug;
use rustc_middle::mir::patch::MirPatch;
@ -9,7 +10,6 @@ use rustc_middle::mir::{
};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_target::abi::{Abi, Variants};
use tracing::trace;
pub(super) struct UnreachableEnumBranching;
@ -65,7 +65,7 @@ fn variant_discriminants<'tcx>(
Variants::Multiple { variants, .. } => variants
.iter_enumerated()
.filter_map(|(idx, layout)| {
(layout.abi != Abi::Uninhabited)
(!layout.is_uninhabited())
.then(|| ty.discriminant_for_variant(tcx, idx).unwrap().val)
})
.collect(),

View File

@ -339,9 +339,7 @@ pub(crate) mod rustc {
// 2. enums that delegate their layout to a variant
// 3. enums with multiple variants
match layout.variants() {
Variants::Single { .. }
if layout.abi.is_uninhabited() && layout.size == Size::ZERO =>
{
Variants::Single { .. } if layout.is_uninhabited() && layout.size == Size::ZERO => {
// The layout representation of uninhabited, ZST enums is
// defined to be like that of the `!` type, as opposed of a
// typical enum. Consequently, they cannot be descended into

View File

@ -10,7 +10,7 @@ pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLa
// Type-level uninhabitedness should always imply ABI uninhabitedness.
if layout.ty.is_privately_uninhabited(tcx, cx.param_env) {
assert!(layout.abi.is_uninhabited());
assert!(layout.is_uninhabited());
}
if layout.size.bytes() % layout.align.abi.bytes() != 0 {
@ -262,9 +262,7 @@ pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLa
)
}
// Skip empty variants.
if variant.size == Size::ZERO
|| variant.fields.count() == 0
|| variant.abi.is_uninhabited()
if variant.size == Size::ZERO || variant.fields.count() == 0 || variant.is_uninhabited()
{
// These are never actually accessed anyway, so we can skip the coherence check
// for them. They also fail that check, since they have