Rollup merge of #133702 - RalfJung:single-variant, r=oli-obk
Variants::Single: do not use invalid VariantIdx for uninhabited enums ~~Stacked on top of https://github.com/rust-lang/rust/pull/133681, only the last commit is new.~~ Currently, `Variants::Single` for an empty enum contains a `VariantIdx` of 0; looking that up in the enum variant list will ICE. That's quite confusing. So let's fix that by adding a new `Variants::Empty` case for types that have 0 variants. try-job: i686-msvc
This commit is contained in:
commit
2a43ce03fb
33 changed files with 186 additions and 165 deletions
|
@ -206,7 +206,7 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
|
|||
let (mut result, mut total) = from_fields_at(*self, Size::ZERO)?;
|
||||
|
||||
match &self.variants {
|
||||
abi::Variants::Single { .. } => {}
|
||||
abi::Variants::Single { .. } | abi::Variants::Empty => {}
|
||||
abi::Variants::Multiple { variants, .. } => {
|
||||
// Treat enum variants like union members.
|
||||
// HACK(eddyb) pretend the `enum` field (discriminant)
|
||||
|
|
|
@ -213,8 +213,9 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
|
|||
&self,
|
||||
) -> LayoutData<FieldIdx, VariantIdx> {
|
||||
let dl = self.cx.data_layout();
|
||||
// This is also used for uninhabited enums, so we use `Variants::Empty`.
|
||||
LayoutData {
|
||||
variants: Variants::Single { index: VariantIdx::new(0) },
|
||||
variants: Variants::Empty,
|
||||
fields: FieldsShape::Primitive,
|
||||
backend_repr: BackendRepr::Uninhabited,
|
||||
largest_niche: None,
|
||||
|
@ -1004,8 +1005,8 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
|
|||
Variants::Multiple { tag, tag_encoding, tag_field, .. } => {
|
||||
Variants::Multiple { tag, tag_encoding, tag_field, variants: best_layout.variants }
|
||||
}
|
||||
Variants::Single { .. } => {
|
||||
panic!("encountered a single-variant enum during multi-variant layout")
|
||||
Variants::Single { .. } | Variants::Empty => {
|
||||
panic!("encountered a single-variant or empty enum during multi-variant layout")
|
||||
}
|
||||
};
|
||||
Ok(best_layout.layout)
|
||||
|
|
|
@ -1504,10 +1504,12 @@ impl BackendRepr {
|
|||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
|
||||
pub enum Variants<FieldIdx: Idx, VariantIdx: Idx> {
|
||||
/// A type with no valid variants. Must be uninhabited.
|
||||
Empty,
|
||||
|
||||
/// Single enum variants, structs/tuples, unions, and all non-ADTs.
|
||||
Single {
|
||||
/// Always 0 for non-enums/generators.
|
||||
/// For enums without a variant, this is an invalid index!
|
||||
/// Always `0` for types that cannot have multiple variants.
|
||||
index: VariantIdx,
|
||||
},
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ pub(crate) fn codegen_set_discriminant<'tcx>(
|
|||
return;
|
||||
}
|
||||
match layout.variants {
|
||||
Variants::Empty => unreachable!("we already handled uninhabited types"),
|
||||
Variants::Single { index } => {
|
||||
assert_eq!(index, variant_index);
|
||||
}
|
||||
|
@ -85,6 +86,7 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
|
|||
}
|
||||
|
||||
let (tag_scalar, tag_field, tag_encoding) = match &layout.variants {
|
||||
Variants::Empty => unreachable!("we already handled uninhabited types"),
|
||||
Variants::Single { index } => {
|
||||
let discr_val = layout
|
||||
.ty
|
||||
|
|
|
@ -212,21 +212,17 @@ pub(super) fn build_enum_type_di_node<'ll, 'tcx>(
|
|||
),
|
||||
|cx, enum_type_di_node| {
|
||||
match enum_type_and_layout.variants {
|
||||
Variants::Single { index: variant_index } => {
|
||||
if enum_adt_def.variants().is_empty() {
|
||||
// Uninhabited enums have Variants::Single. We don't generate
|
||||
// any members for them.
|
||||
return smallvec![];
|
||||
}
|
||||
|
||||
build_single_variant_union_fields(
|
||||
cx,
|
||||
enum_adt_def,
|
||||
enum_type_and_layout,
|
||||
enum_type_di_node,
|
||||
variant_index,
|
||||
)
|
||||
Variants::Empty => {
|
||||
// We don't generate any members for uninhabited types.
|
||||
return smallvec![];
|
||||
}
|
||||
Variants::Single { index: variant_index } => build_single_variant_union_fields(
|
||||
cx,
|
||||
enum_adt_def,
|
||||
enum_type_and_layout,
|
||||
enum_type_di_node,
|
||||
variant_index,
|
||||
),
|
||||
Variants::Multiple {
|
||||
tag_encoding: TagEncoding::Direct,
|
||||
ref variants,
|
||||
|
@ -303,6 +299,7 @@ pub(super) fn build_coroutine_di_node<'ll, 'tcx>(
|
|||
)
|
||||
}
|
||||
Variants::Single { .. }
|
||||
| Variants::Empty
|
||||
| Variants::Multiple { tag_encoding: TagEncoding::Niche { .. }, .. } => {
|
||||
bug!(
|
||||
"Encountered coroutine with non-direct-tag layout: {:?}",
|
||||
|
|
|
@ -392,7 +392,7 @@ fn compute_discriminant_value<'ll, 'tcx>(
|
|||
variant_index: VariantIdx,
|
||||
) -> DiscrResult {
|
||||
match enum_type_and_layout.layout.variants() {
|
||||
&Variants::Single { .. } => DiscrResult::NoDiscriminant,
|
||||
&Variants::Single { .. } | &Variants::Empty => DiscrResult::NoDiscriminant,
|
||||
&Variants::Multiple { tag_encoding: TagEncoding::Direct, .. } => DiscrResult::Value(
|
||||
enum_type_and_layout.ty.discriminant_for_variant(cx.tcx, variant_index).unwrap().val,
|
||||
),
|
||||
|
|
|
@ -358,8 +358,8 @@ fn build_discr_member_di_node<'ll, 'tcx>(
|
|||
let containing_scope = enum_or_coroutine_type_di_node;
|
||||
|
||||
match enum_or_coroutine_type_and_layout.layout.variants() {
|
||||
// A single-variant enum has no discriminant.
|
||||
&Variants::Single { .. } => None,
|
||||
// A single-variant or no-variant enum has no discriminant.
|
||||
&Variants::Single { .. } | &Variants::Empty => None,
|
||||
|
||||
&Variants::Multiple { tag_field, .. } => {
|
||||
let tag_base_type = tag_base_type(cx.tcx, enum_or_coroutine_type_and_layout);
|
||||
|
|
|
@ -38,7 +38,7 @@ fn uncached_llvm_type<'a, 'tcx>(
|
|||
if let (&ty::Adt(def, _), &Variants::Single { index }) =
|
||||
(layout.ty.kind(), &layout.variants)
|
||||
{
|
||||
if def.is_enum() && !def.variants().is_empty() {
|
||||
if def.is_enum() {
|
||||
write!(&mut name, "::{}", def.variant(index).name).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@ fn tag_base_type_opt<'tcx>(
|
|||
});
|
||||
|
||||
match enum_type_and_layout.layout.variants() {
|
||||
// A single-variant enum has no discriminant.
|
||||
Variants::Single { .. } => None,
|
||||
// A single-variant or no-variant enum has no discriminant.
|
||||
Variants::Single { .. } | Variants::Empty => None,
|
||||
|
||||
Variants::Multiple { tag_encoding: TagEncoding::Niche { .. }, tag, .. } => {
|
||||
// Niche tags are always normalized to unsized integers of the correct size.
|
||||
|
|
|
@ -243,6 +243,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
|
|||
return bx.cx().const_poison(cast_to);
|
||||
}
|
||||
let (tag_scalar, tag_encoding, tag_field) = match self.layout.variants {
|
||||
Variants::Empty => unreachable!("we already handled uninhabited types"),
|
||||
Variants::Single { index } => {
|
||||
let discr_val = self
|
||||
.layout
|
||||
|
@ -365,9 +366,9 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
|
|||
return;
|
||||
}
|
||||
match self.layout.variants {
|
||||
Variants::Single { index } => {
|
||||
assert_eq!(index, variant_index);
|
||||
}
|
||||
Variants::Empty => unreachable!("we already handled uninhabited types"),
|
||||
Variants::Single { index } => assert_eq!(index, variant_index),
|
||||
|
||||
Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } => {
|
||||
let ptr = self.project_field(bx, tag_field);
|
||||
let to =
|
||||
|
|
|
@ -44,7 +44,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read discriminant, return the runtime value as well as the variant index.
|
||||
/// Read discriminant, return the variant index.
|
||||
/// Can also legally be called on non-enums (e.g. through the discriminant_value intrinsic)!
|
||||
///
|
||||
/// Will never return an uninhabited variant.
|
||||
|
@ -65,21 +65,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
// We use "tag" to refer to how the discriminant is encoded in memory, which can be either
|
||||
// straight-forward (`TagEncoding::Direct`) or with a niche (`TagEncoding::Niche`).
|
||||
let (tag_scalar_layout, tag_encoding, tag_field) = match op.layout().variants {
|
||||
Variants::Empty => {
|
||||
throw_ub!(UninhabitedEnumVariantRead(None));
|
||||
}
|
||||
Variants::Single { index } => {
|
||||
// Do some extra checks on enums.
|
||||
if ty.is_enum() {
|
||||
// Hilariously, `Single` is used even for 0-variant enums.
|
||||
// (See https://github.com/rust-lang/rust/issues/89765).
|
||||
if ty.ty_adt_def().unwrap().variants().is_empty() {
|
||||
throw_ub!(UninhabitedEnumVariantRead(index))
|
||||
}
|
||||
if op.layout().is_uninhabited() {
|
||||
// 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).is_uninhabited() {
|
||||
throw_ub!(UninhabitedEnumVariantRead(index))
|
||||
}
|
||||
// for uninhabited enums.
|
||||
throw_ub!(UninhabitedEnumVariantRead(Some(index)));
|
||||
}
|
||||
// Since the type is inhabited, there must be an index.
|
||||
return interp_ok(index);
|
||||
}
|
||||
Variants::Multiple { tag, ref tag_encoding, tag_field, .. } => {
|
||||
|
@ -199,11 +195,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
// `uninhabited_enum_branching` MIR pass. It also ensures consistency with
|
||||
// `write_discriminant`.
|
||||
if op.layout().for_variant(self, index).is_uninhabited() {
|
||||
throw_ub!(UninhabitedEnumVariantRead(index))
|
||||
throw_ub!(UninhabitedEnumVariantRead(Some(index)))
|
||||
}
|
||||
interp_ok(index)
|
||||
}
|
||||
|
||||
/// Read discriminant, return the user-visible discriminant.
|
||||
/// Can also legally be called on non-enums (e.g. through the discriminant_value intrinsic)!
|
||||
pub fn discriminant_for_variant(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
|
@ -243,6 +241,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
|
||||
match layout.variants {
|
||||
abi::Variants::Empty => unreachable!("we already handled uninhabited types"),
|
||||
abi::Variants::Single { .. } => {
|
||||
// The tag of a `Single` enum is like the tag of the niched
|
||||
// variant: there's no tag as the discriminant is encoded
|
||||
|
|
|
@ -302,7 +302,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
|||
};
|
||||
}
|
||||
}
|
||||
Variants::Single { .. } => {}
|
||||
Variants::Single { .. } | Variants::Empty => {}
|
||||
}
|
||||
|
||||
// Now we know we are projecting to a field, so figure out which one.
|
||||
|
@ -344,6 +344,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
|||
// Inside a variant
|
||||
PathElem::Field(def.variant(index).fields[FieldIdx::from_usize(field)].name)
|
||||
}
|
||||
Variants::Empty => panic!("there is no field in Variants::Empty types"),
|
||||
Variants::Multiple { .. } => bug!("we handled variants above"),
|
||||
}
|
||||
}
|
||||
|
@ -1010,7 +1011,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
|||
}
|
||||
// Don't forget potential other variants.
|
||||
match &layout.variants {
|
||||
Variants::Single { .. } => {
|
||||
Variants::Single { .. } | Variants::Empty => {
|
||||
// Fully handled above.
|
||||
}
|
||||
Variants::Multiple { variants, .. } => {
|
||||
|
|
|
@ -218,8 +218,8 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
|
|||
// recurse with the inner type
|
||||
self.visit_variant(v, idx, &inner)?;
|
||||
}
|
||||
// For single-variant layouts, we already did anything there is to do.
|
||||
Variants::Single { .. } => {}
|
||||
// For single-variant layouts, we already did everything there is to do.
|
||||
Variants::Single { .. } | Variants::Empty => {}
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
|
|
|
@ -155,6 +155,7 @@ fn check_validity_requirement_lax<'tcx>(
|
|||
}
|
||||
|
||||
match &this.variants {
|
||||
Variants::Empty => return Ok(false),
|
||||
Variants::Single { .. } => {
|
||||
// All fields of this single variant have already been checked above, there is nothing
|
||||
// else to do.
|
||||
|
|
|
@ -392,7 +392,7 @@ pub enum UndefinedBehaviorInfo<'tcx> {
|
|||
/// A discriminant of an uninhabited enum variant is written.
|
||||
UninhabitedEnumVariantWritten(VariantIdx),
|
||||
/// An uninhabited enum variant is projected.
|
||||
UninhabitedEnumVariantRead(VariantIdx),
|
||||
UninhabitedEnumVariantRead(Option<VariantIdx>),
|
||||
/// Trying to set discriminant to the niched variant, but the value does not match.
|
||||
InvalidNichedEnumVariantWritten { enum_ty: Ty<'tcx> },
|
||||
/// ABI-incompatible argument types.
|
||||
|
|
|
@ -734,21 +734,22 @@ where
|
|||
let layout = match this.variants {
|
||||
Variants::Single { index }
|
||||
// If all variants but one are uninhabited, the variant layout is the enum layout.
|
||||
if index == variant_index &&
|
||||
// Don't confuse variants of uninhabited enums with the enum itself.
|
||||
// For more details see https://github.com/rust-lang/rust/issues/69763.
|
||||
this.fields != FieldsShape::Primitive =>
|
||||
if index == variant_index =>
|
||||
{
|
||||
this.layout
|
||||
}
|
||||
|
||||
Variants::Single { index } => {
|
||||
Variants::Single { .. } | Variants::Empty => {
|
||||
// Single-variant and no-variant enums *can* have other variants, but those are
|
||||
// uninhabited. Produce a layout that has the right fields for that variant, so that
|
||||
// the rest of the compiler can project fields etc as usual.
|
||||
|
||||
let tcx = cx.tcx();
|
||||
let typing_env = cx.typing_env();
|
||||
|
||||
// Deny calling for_variant more than once for non-Single enums.
|
||||
if let Ok(original_layout) = tcx.layout_of(typing_env.as_query_input(this.ty)) {
|
||||
assert_eq!(original_layout.variants, Variants::Single { index });
|
||||
assert_eq!(original_layout.variants, this.variants);
|
||||
}
|
||||
|
||||
let fields = match this.ty.kind() {
|
||||
|
@ -902,6 +903,7 @@ where
|
|||
),
|
||||
|
||||
ty::Coroutine(def_id, args) => match this.variants {
|
||||
Variants::Empty => unreachable!(),
|
||||
Variants::Single { index } => TyMaybeWithLayout::Ty(
|
||||
args.as_coroutine()
|
||||
.state_tys(def_id, tcx)
|
||||
|
@ -927,6 +929,7 @@ where
|
|||
let field = &def.variant(index).fields[FieldIdx::from_usize(i)];
|
||||
TyMaybeWithLayout::Ty(field.ty(tcx, args))
|
||||
}
|
||||
Variants::Empty => panic!("there is no field in Variants::Empty types"),
|
||||
|
||||
// Discriminant field for enums (where applicable).
|
||||
Variants::Multiple { tag, .. } => {
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
//! Likewise, applying the optimisation can create a lot of new MIR, so we bound the instruction
|
||||
//! cost by `MAX_COST`.
|
||||
|
||||
use rustc_abi::{TagEncoding, Variants};
|
||||
use rustc_arena::DroplessArena;
|
||||
use rustc_const_eval::const_eval::DummyMachine;
|
||||
use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable};
|
||||
|
@ -565,31 +564,15 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
StatementKind::SetDiscriminant { box place, variant_index } => {
|
||||
let Some(discr_target) = self.map.find_discr(place.as_ref()) else { return };
|
||||
let enum_ty = place.ty(self.body, self.tcx).ty;
|
||||
// `SetDiscriminant` may be a no-op if the assigned variant is the untagged variant
|
||||
// of a niche encoding. If we cannot ensure that we write to the discriminant, do
|
||||
// nothing.
|
||||
let Ok(enum_layout) = self.ecx.layout_of(enum_ty) else {
|
||||
// `SetDiscriminant` guarantees that the discriminant is now `variant_index`.
|
||||
// Even if the discriminant write does nothing due to niches, it is UB to set the
|
||||
// discriminant when the data does not encode the desired discriminant.
|
||||
let Some(discr) =
|
||||
self.ecx.discriminant_for_variant(enum_ty, *variant_index).discard_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let writes_discriminant = match enum_layout.variants {
|
||||
Variants::Single { index } => {
|
||||
assert_eq!(index, *variant_index);
|
||||
true
|
||||
}
|
||||
Variants::Multiple { tag_encoding: TagEncoding::Direct, .. } => true,
|
||||
Variants::Multiple {
|
||||
tag_encoding: TagEncoding::Niche { untagged_variant, .. },
|
||||
..
|
||||
} => *variant_index != untagged_variant,
|
||||
};
|
||||
if writes_discriminant {
|
||||
let Some(discr) =
|
||||
self.ecx.discriminant_for_variant(enum_ty, *variant_index).discard_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.process_immediate(bb, discr_target, discr, state);
|
||||
}
|
||||
self.process_immediate(bb, discr_target, discr, state);
|
||||
}
|
||||
// If we expect `lhs ?= true`, we have an opportunity if we assume `lhs == true`.
|
||||
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(
|
||||
|
|
|
@ -216,7 +216,7 @@ impl EnumSizeOpt {
|
|||
};
|
||||
let layout = tcx.layout_of(typing_env.as_query_input(ty)).ok()?;
|
||||
let variants = match &layout.variants {
|
||||
Variants::Single { .. } => return None,
|
||||
Variants::Single { .. } | Variants::Empty => return None,
|
||||
Variants::Multiple { tag_encoding: TagEncoding::Niche { .. }, .. } => return None,
|
||||
|
||||
Variants::Multiple { variants, .. } if variants.len() <= 1 => return None,
|
||||
|
|
|
@ -54,6 +54,10 @@ fn variant_discriminants<'tcx>(
|
|||
tcx: TyCtxt<'tcx>,
|
||||
) -> FxHashSet<u128> {
|
||||
match &layout.variants {
|
||||
Variants::Empty => {
|
||||
// Uninhabited, no valid discriminant.
|
||||
FxHashSet::default()
|
||||
}
|
||||
Variants::Single { index } => {
|
||||
let mut res = FxHashSet::default();
|
||||
res.insert(
|
||||
|
|
|
@ -167,6 +167,7 @@ impl<'tcx> Stable<'tcx> for rustc_abi::Variants<rustc_abi::FieldIdx, rustc_abi::
|
|||
rustc_abi::Variants::Single { index } => {
|
||||
VariantsShape::Single { index: index.stable(tables) }
|
||||
}
|
||||
rustc_abi::Variants::Empty => VariantsShape::Empty,
|
||||
rustc_abi::Variants::Multiple { tag, tag_encoding, tag_field, variants } => {
|
||||
VariantsShape::Multiple {
|
||||
tag: tag.stable(tables),
|
||||
|
|
|
@ -116,7 +116,7 @@ where
|
|||
FieldsShape::Arbitrary { .. } => {
|
||||
match arg_layout.variants {
|
||||
abi::Variants::Multiple { .. } => return Err(CannotUseFpConv),
|
||||
abi::Variants::Single { .. } => (),
|
||||
abi::Variants::Single { .. } | abi::Variants::Empty => (),
|
||||
}
|
||||
for i in arg_layout.fields.index_by_increasing_offset() {
|
||||
let field = arg_layout.field(cx, i);
|
||||
|
|
|
@ -122,7 +122,7 @@ where
|
|||
FieldsShape::Arbitrary { .. } => {
|
||||
match arg_layout.variants {
|
||||
abi::Variants::Multiple { .. } => return Err(CannotUseFpConv),
|
||||
abi::Variants::Single { .. } => (),
|
||||
abi::Variants::Single { .. } | abi::Variants::Empty => (),
|
||||
}
|
||||
for i in arg_layout.fields.index_by_increasing_offset() {
|
||||
let field = arg_layout.field(cx, i);
|
||||
|
|
|
@ -65,7 +65,7 @@ where
|
|||
}
|
||||
|
||||
match &layout.variants {
|
||||
abi::Variants::Single { .. } => {}
|
||||
abi::Variants::Single { .. } | abi::Variants::Empty => {}
|
||||
abi::Variants::Multiple { variants, .. } => {
|
||||
// Treat enum variants like union members.
|
||||
for variant_idx in variants.indices() {
|
||||
|
|
|
@ -338,16 +338,11 @@ pub(crate) mod rustc {
|
|||
};
|
||||
|
||||
match layout.variants() {
|
||||
Variants::Empty => Ok(Self::uninhabited()),
|
||||
Variants::Single { index } => {
|
||||
// Hilariously, `Single` is used even for 0-variant enums;
|
||||
// `index` is just junk in that case.
|
||||
if ty.ty_adt_def().unwrap().variants().is_empty() {
|
||||
Ok(Self::uninhabited())
|
||||
} else {
|
||||
// `Variants::Single` on enums with variants denotes that
|
||||
// the enum delegates its layout to the variant at `index`.
|
||||
layout_of_variant(*index, None)
|
||||
}
|
||||
// `Variants::Single` on enums with variants denotes that
|
||||
// the enum delegates its layout to the variant at `index`.
|
||||
layout_of_variant(*index, None)
|
||||
}
|
||||
Variants::Multiple { tag, tag_encoding, tag_field, .. } => {
|
||||
// `Variants::Multiple` denotes an enum with multiple
|
||||
|
@ -500,6 +495,10 @@ pub(crate) mod rustc {
|
|||
(ty, layout): (Ty<'tcx>, Layout<'tcx>),
|
||||
i: FieldIdx,
|
||||
) -> Ty<'tcx> {
|
||||
// We cannot use `ty_and_layout_field` to retrieve the field type, since
|
||||
// `ty_and_layout_field` erases regions in the returned type. We must
|
||||
// not erase regions here, since we may need to ultimately emit outlives
|
||||
// obligations as a consequence of the transmutability analysis.
|
||||
match ty.kind() {
|
||||
ty::Adt(def, args) => {
|
||||
match layout.variants {
|
||||
|
@ -507,6 +506,7 @@ pub(crate) mod rustc {
|
|||
let field = &def.variant(index).fields[i];
|
||||
field.ty(cx.tcx(), args)
|
||||
}
|
||||
Variants::Empty => panic!("there is no field in Variants::Empty types"),
|
||||
// Discriminant field for enums (where applicable).
|
||||
Variants::Multiple { tag, .. } => {
|
||||
assert_eq!(i.as_usize(), 0);
|
||||
|
|
|
@ -1104,15 +1104,13 @@ fn variant_info_for_adt<'tcx>(
|
|||
};
|
||||
|
||||
match layout.variants {
|
||||
Variants::Empty => (vec![], None),
|
||||
|
||||
Variants::Single { index } => {
|
||||
if !adt_def.variants().is_empty() && layout.fields != FieldsShape::Primitive {
|
||||
debug!("print-type-size `{:#?}` variant {}", layout, adt_def.variant(index).name);
|
||||
let variant_def = &adt_def.variant(index);
|
||||
let fields: Vec<_> = variant_def.fields.iter().map(|f| f.name).collect();
|
||||
(vec![build_variant_info(Some(variant_def.name), &fields, layout)], None)
|
||||
} else {
|
||||
(vec![], None)
|
||||
}
|
||||
debug!("print-type-size `{:#?}` variant {}", layout, adt_def.variant(index).name);
|
||||
let variant_def = &adt_def.variant(index);
|
||||
let fields: Vec<_> = variant_def.fields.iter().map(|f| f.name).collect();
|
||||
(vec![build_variant_info(Some(variant_def.name), &fields, layout)], None)
|
||||
}
|
||||
|
||||
Variants::Multiple { tag, ref tag_encoding, .. } => {
|
||||
|
|
|
@ -241,63 +241,81 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou
|
|||
|
||||
check_layout_abi(cx, layout);
|
||||
|
||||
if let Variants::Multiple { variants, tag, tag_encoding, .. } = &layout.variants {
|
||||
if let TagEncoding::Niche { niche_start, untagged_variant, niche_variants } = tag_encoding {
|
||||
let niche_size = tag.size(cx);
|
||||
assert!(*niche_start <= niche_size.unsigned_int_max());
|
||||
for (idx, variant) in variants.iter_enumerated() {
|
||||
// Ensure all inhabited variants are accounted for.
|
||||
if !variant.is_uninhabited() {
|
||||
assert!(idx == *untagged_variant || niche_variants.contains(&idx));
|
||||
}
|
||||
match &layout.variants {
|
||||
Variants::Empty => {
|
||||
assert!(layout.is_uninhabited());
|
||||
}
|
||||
Variants::Single { index } => {
|
||||
if let Some(variants) = layout.ty.variant_range(tcx) {
|
||||
assert!(variants.contains(index));
|
||||
} else {
|
||||
// Types without variants use `0` as dummy variant index.
|
||||
assert!(index.as_u32() == 0);
|
||||
}
|
||||
}
|
||||
for variant in variants.iter() {
|
||||
// No nested "multiple".
|
||||
assert_matches!(variant.variants, Variants::Single { .. });
|
||||
// Variants should have the same or a smaller size as the full thing,
|
||||
// and same for alignment.
|
||||
if variant.size > layout.size {
|
||||
bug!(
|
||||
"Type with size {} bytes has variant with size {} bytes: {layout:#?}",
|
||||
layout.size.bytes(),
|
||||
variant.size.bytes(),
|
||||
)
|
||||
}
|
||||
if variant.align.abi > layout.align.abi {
|
||||
bug!(
|
||||
"Type with alignment {} bytes has variant with alignment {} bytes: {layout:#?}",
|
||||
layout.align.abi.bytes(),
|
||||
variant.align.abi.bytes(),
|
||||
)
|
||||
}
|
||||
// Skip empty variants.
|
||||
if variant.size == Size::ZERO || variant.fields.count() == 0 || variant.is_uninhabited()
|
||||
Variants::Multiple { variants, tag, tag_encoding, .. } => {
|
||||
if let TagEncoding::Niche { niche_start, untagged_variant, niche_variants } =
|
||||
tag_encoding
|
||||
{
|
||||
// These are never actually accessed anyway, so we can skip the coherence check
|
||||
// for them. They also fail that check, since they have
|
||||
// `Aggregate`/`Uninhabited` ABI even when the main type is
|
||||
// `Scalar`/`ScalarPair`. (Note that sometimes, variants with fields have size
|
||||
// 0, and sometimes, variants without fields have non-0 size.)
|
||||
continue;
|
||||
}
|
||||
// The top-level ABI and the ABI of the variants should be coherent.
|
||||
let scalar_coherent =
|
||||
|s1: Scalar, s2: Scalar| s1.size(cx) == s2.size(cx) && s1.align(cx) == s2.align(cx);
|
||||
let abi_coherent = match (layout.backend_repr, variant.backend_repr) {
|
||||
(BackendRepr::Scalar(s1), BackendRepr::Scalar(s2)) => scalar_coherent(s1, s2),
|
||||
(BackendRepr::ScalarPair(a1, b1), BackendRepr::ScalarPair(a2, b2)) => {
|
||||
scalar_coherent(a1, a2) && scalar_coherent(b1, b2)
|
||||
let niche_size = tag.size(cx);
|
||||
assert!(*niche_start <= niche_size.unsigned_int_max());
|
||||
for (idx, variant) in variants.iter_enumerated() {
|
||||
// Ensure all inhabited variants are accounted for.
|
||||
if !variant.is_uninhabited() {
|
||||
assert!(idx == *untagged_variant || niche_variants.contains(&idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
for variant in variants.iter() {
|
||||
// No nested "multiple".
|
||||
assert_matches!(variant.variants, Variants::Single { .. });
|
||||
// Variants should have the same or a smaller size as the full thing,
|
||||
// and same for alignment.
|
||||
if variant.size > layout.size {
|
||||
bug!(
|
||||
"Type with size {} bytes has variant with size {} bytes: {layout:#?}",
|
||||
layout.size.bytes(),
|
||||
variant.size.bytes(),
|
||||
)
|
||||
}
|
||||
if variant.align.abi > layout.align.abi {
|
||||
bug!(
|
||||
"Type with alignment {} bytes has variant with alignment {} bytes: {layout:#?}",
|
||||
layout.align.abi.bytes(),
|
||||
variant.align.abi.bytes(),
|
||||
)
|
||||
}
|
||||
// Skip empty variants.
|
||||
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
|
||||
// `Aggregate`/`Uninhabited` ABI even when the main type is
|
||||
// `Scalar`/`ScalarPair`. (Note that sometimes, variants with fields have size
|
||||
// 0, and sometimes, variants without fields have non-0 size.)
|
||||
continue;
|
||||
}
|
||||
// The top-level ABI and the ABI of the variants should be coherent.
|
||||
let scalar_coherent = |s1: Scalar, s2: Scalar| {
|
||||
s1.size(cx) == s2.size(cx) && s1.align(cx) == s2.align(cx)
|
||||
};
|
||||
let abi_coherent = match (layout.backend_repr, variant.backend_repr) {
|
||||
(BackendRepr::Scalar(s1), BackendRepr::Scalar(s2)) => scalar_coherent(s1, s2),
|
||||
(BackendRepr::ScalarPair(a1, b1), BackendRepr::ScalarPair(a2, b2)) => {
|
||||
scalar_coherent(a1, a2) && scalar_coherent(b1, b2)
|
||||
}
|
||||
(BackendRepr::Uninhabited, _) => true,
|
||||
(BackendRepr::Memory { .. }, _) => true,
|
||||
_ => false,
|
||||
};
|
||||
if !abi_coherent {
|
||||
bug!(
|
||||
"Variant ABI is incompatible with top-level ABI:\nvariant={:#?}\nTop-level: {layout:#?}",
|
||||
variant
|
||||
);
|
||||
}
|
||||
(BackendRepr::Uninhabited, _) => true,
|
||||
(BackendRepr::Memory { .. }, _) => true,
|
||||
_ => false,
|
||||
};
|
||||
if !abi_coherent {
|
||||
bug!(
|
||||
"Variant ABI is incompatible with top-level ABI:\nvariant={:#?}\nTop-level: {layout:#?}",
|
||||
variant
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,6 +180,9 @@ impl FieldsShape {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
||||
pub enum VariantsShape {
|
||||
/// A type with no valid variants. Must be uninhabited.
|
||||
Empty,
|
||||
|
||||
/// Single enum variants, structs/tuples, unions, and all non-ADTs.
|
||||
Single { index: VariantIdx },
|
||||
|
||||
|
|
|
@ -605,7 +605,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// `UnsafeCell` action.
|
||||
(self.unsafe_cell_action)(v)
|
||||
}
|
||||
Variants::Single { .. } => {
|
||||
Variants::Single { .. } | Variants::Empty => {
|
||||
// Proceed further, try to find where exactly that `UnsafeCell`
|
||||
// is hiding.
|
||||
self.walk_value(v)
|
||||
|
|
|
@ -813,7 +813,7 @@ impl Evaluator<'_> {
|
|||
ProjectionElem::Field(Either::Left(f)) => {
|
||||
let layout = self.layout(&prev_ty)?;
|
||||
let variant_layout = match &layout.variants {
|
||||
Variants::Single { .. } => &layout,
|
||||
Variants::Single { .. } | Variants::Empty => &layout,
|
||||
Variants::Multiple { variants, .. } => {
|
||||
&variants[match f.parent {
|
||||
hir_def::VariantId::EnumVariantId(it) => {
|
||||
|
@ -1638,6 +1638,7 @@ impl Evaluator<'_> {
|
|||
return Ok(0);
|
||||
};
|
||||
match &layout.variants {
|
||||
Variants::Empty => unreachable!(),
|
||||
Variants::Single { index } => {
|
||||
let r = self.const_eval_discriminant(self.db.enum_data(e).variants[index.0].0)?;
|
||||
Ok(r)
|
||||
|
@ -1800,7 +1801,7 @@ impl Evaluator<'_> {
|
|||
}
|
||||
let layout = self.layout_adt(adt, subst)?;
|
||||
Ok(match &layout.variants {
|
||||
Variants::Single { .. } => (layout.size.bytes_usize(), layout, None),
|
||||
Variants::Single { .. } | Variants::Empty => (layout.size.bytes_usize(), layout, None),
|
||||
Variants::Multiple { variants, tag, tag_encoding, .. } => {
|
||||
let enum_variant_id = match it {
|
||||
VariantId::EnumVariantId(it) => it,
|
||||
|
|
|
@ -334,6 +334,7 @@ pub(crate) fn detect_variant_from_bytes<'a>(
|
|||
e: EnumId,
|
||||
) -> Option<(EnumVariantId, &'a Layout)> {
|
||||
let (var_id, var_layout) = match &layout.variants {
|
||||
hir_def::layout::Variants::Empty => unreachable!(),
|
||||
hir_def::layout::Variants::Single { index } => {
|
||||
(db.enum_data(e).variants[index.0].0, layout)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
_2 = E::<char>::A;
|
||||
discriminant(_2) = 1;
|
||||
_1 = discriminant(_2);
|
||||
switchInt(copy _1) -> [0: bb1, otherwise: bb2];
|
||||
- switchInt(copy _1) -> [0: bb1, otherwise: bb2];
|
||||
+ goto -> bb2;
|
||||
}
|
||||
|
||||
bb1: {
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
_2 = E::<T>::A;
|
||||
discriminant(_2) = 1;
|
||||
_1 = discriminant(_2);
|
||||
switchInt(copy _1) -> [0: bb1, otherwise: bb2];
|
||||
- switchInt(copy _1) -> [0: bb1, otherwise: bb2];
|
||||
+ goto -> bb2;
|
||||
}
|
||||
|
||||
bb1: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// `SetDiscriminant` does not actually write anything if the chosen variant is the untagged variant
|
||||
// of a niche encoding. Verify that we do not thread over this case.
|
||||
// of a niche encoding. However, it is UB to call `SetDiscriminant` with the untagged variant if the
|
||||
// value currently encodes a different variant. Verify that we do correctly thread in this case.
|
||||
//@ test-mir-pass: JumpThreading
|
||||
|
||||
#![feature(custom_mir)]
|
||||
|
@ -16,20 +17,21 @@ enum E<T> {
|
|||
#[custom_mir(dialect = "runtime")]
|
||||
pub fn f() -> usize {
|
||||
// CHECK-LABEL: fn f(
|
||||
// CHECK-NOT: goto
|
||||
// CHECK: switchInt(
|
||||
// CHECK-NOT: goto
|
||||
// CHECK-NOT: switchInt
|
||||
// CHECK: goto
|
||||
// CHECK-NOT: switchInt
|
||||
mir! {
|
||||
let a: isize;
|
||||
let e: E<char>;
|
||||
{
|
||||
e = E::A;
|
||||
SetDiscriminant(e, 1);
|
||||
SetDiscriminant(e, 1); // UB!
|
||||
a = Discriminant(e);
|
||||
match a {
|
||||
0 => bb0,
|
||||
_ => bb1,
|
||||
}
|
||||
|
||||
}
|
||||
bb0 = {
|
||||
RET = 0;
|
||||
|
@ -46,15 +48,15 @@ pub fn f() -> usize {
|
|||
#[custom_mir(dialect = "runtime")]
|
||||
pub fn generic<T>() -> usize {
|
||||
// CHECK-LABEL: fn generic(
|
||||
// CHECK-NOT: goto
|
||||
// CHECK: switchInt(
|
||||
// CHECK-NOT: goto
|
||||
// CHECK-NOT: switchInt
|
||||
// CHECK: goto
|
||||
// CHECK-NOT: switchInt
|
||||
mir! {
|
||||
let a: isize;
|
||||
let e: E<T>;
|
||||
{
|
||||
e = E::A;
|
||||
SetDiscriminant(e, 1);
|
||||
SetDiscriminant(e, 1); // UB!
|
||||
a = Discriminant(e);
|
||||
match a {
|
||||
0 => bb0,
|
||||
|
@ -72,6 +74,7 @@ pub fn generic<T>() -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: fn main(
|
||||
fn main() {
|
||||
assert_eq!(f(), 0);
|
||||
assert_eq!(generic::<char>(), 0);
|
||||
|
|
Loading…
Add table
Reference in a new issue