Skip to main content

rustc_const_eval/interpret/
discriminant.rs

1//! Functions for reading and writing discriminants of multi-variant layouts (enums and coroutines).
2
3use rustc_abi::{self as abi, FieldIdx, TagEncoding, VariantIdx, Variants};
4use rustc_middle::mir;
5use rustc_middle::ty::layout::{PrimitiveExt, TyAndLayout};
6use rustc_middle::ty::{self, CoroutineArgsExt, ScalarInt, Ty};
7use rustc_span::span_bug;
8use tracing::{instrument, trace};
9
10use super::{
11    ImmTy, InterpCx, InterpResult, Machine, Projectable, Scalar, Writeable, err_ub, interp_ok,
12    throw_ub,
13};
14
15impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
16    /// Writes the discriminant of the given variant.
17    ///
18    /// If the variant is uninhabited, this is UB.
19    {}
#[allow(clippy :: suspicious_else_formatting)]
{
    let __tracing_attr_span;
    let __tracing_attr_guard;
    if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() ||
            { false } {
        __tracing_attr_span =
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("write_discriminant",
                                    "rustc_const_eval::interpret::discriminant",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs"),
                                    ::tracing_core::__macro_support::Option::Some(19u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_const_eval::interpret::discriminant"),
                                    ::tracing_core::field::FieldSet::new(&[{
                                                        const NAME:
                                                            ::tracing::__macro_support::FieldName<{
                                                                ::tracing::__macro_support::FieldName::len("variant_index")
                                                            }> =
                                                            ::tracing::__macro_support::FieldName::new("variant_index");
                                                        NAME.as_str()
                                                    },
                                                    {
                                                        const NAME:
                                                            ::tracing::__macro_support::FieldName<{
                                                                ::tracing::__macro_support::FieldName::len("dest")
                                                            }> =
                                                            ::tracing::__macro_support::FieldName::new("dest");
                                                        NAME.as_str()
                                                    }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::SPAN)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let mut interest = ::tracing::subscriber::Interest::never();
                if ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::LevelFilter::current() &&
                            { interest = __CALLSITE.interest(); !interest.is_never() }
                        &&
                        ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                            interest) {
                    let meta = __CALLSITE.metadata();
                    ::tracing::Span::new(meta,
                        &{
                                #[allow(unused_imports)]
                                use ::tracing::field::{debug, display, Value};
                                meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&variant_index)
                                                            as &dyn ::tracing::field::Value)),
                                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&dest)
                                                            as &dyn ::tracing::field::Value))])
                            })
                } else {
                    let span =
                        ::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
                    {};
                    span
                }
            };
        __tracing_attr_guard = __tracing_attr_span.enter();
    }

    #[warn(clippy :: suspicious_else_formatting)]
    {

        #[allow(unknown_lints, unreachable_code, clippy ::
        diverging_sub_expression, clippy :: empty_loop, clippy ::
        let_unit_value, clippy :: let_with_type_underscore, clippy ::
        needless_return, clippy :: unreachable)]
        if false {
            let __tracing_attr_fake_return: InterpResult<'tcx> = loop {};
            return __tracing_attr_fake_return;
        }
        {
            match self.tag_for_variant(dest.layout(), variant_index)? {
                Some((tag, tag_field)) => {
                    let tag_dest = self.project_field(dest, tag_field)?;
                    self.write_scalar(tag, &tag_dest)
                }
                None => {
                    let actual_variant =
                        self.read_discriminant(&dest.to_op(self)?)?;
                    if actual_variant != variant_index {
                        do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidNichedEnumVariantWritten {
                                    enum_ty: dest.layout().ty,
                                });
                    }
                    interp_ok(())
                }
            }
        }
    }
}#[instrument(skip(self), level = "trace")]
20    pub fn write_discriminant(
21        &mut self,
22        variant_index: VariantIdx,
23        dest: &impl Writeable<'tcx, M::Provenance>,
24    ) -> InterpResult<'tcx> {
25        match self.tag_for_variant(dest.layout(), variant_index)? {
26            Some((tag, tag_field)) => {
27                // No need to validate that the discriminant here because the
28                // `TyAndLayout::for_variant()` call earlier already checks the
29                // variant is valid.
30                let tag_dest = self.project_field(dest, tag_field)?;
31                self.write_scalar(tag, &tag_dest)
32            }
33            None => {
34                // No need to write the tag here, because an untagged variant is
35                // implicitly encoded. For `Niche`-optimized enums, this works by
36                // simply by having a value that is outside the niche variants.
37                // But what if the data stored here does not actually encode
38                // this variant? That would be bad! So let's double-check...
39                let actual_variant = self.read_discriminant(&dest.to_op(self)?)?;
40                if actual_variant != variant_index {
41                    throw_ub!(InvalidNichedEnumVariantWritten { enum_ty: dest.layout().ty });
42                }
43                interp_ok(())
44            }
45        }
46    }
47
48    /// Read discriminant, return the variant index.
49    /// Can also legally be called on non-enums (e.g. through the discriminant_value intrinsic)!
50    ///
51    /// Will never return an uninhabited variant.
52    {}
#[allow(clippy :: suspicious_else_formatting)]
{
    let __tracing_attr_span;
    let __tracing_attr_guard;
    if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() ||
            { false } {
        __tracing_attr_span =
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("read_discriminant",
                                    "rustc_const_eval::interpret::discriminant",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs"),
                                    ::tracing_core::__macro_support::Option::Some(52u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_const_eval::interpret::discriminant"),
                                    ::tracing_core::field::FieldSet::new(&[{
                                                        const NAME:
                                                            ::tracing::__macro_support::FieldName<{
                                                                ::tracing::__macro_support::FieldName::len("op")
                                                            }> =
                                                            ::tracing::__macro_support::FieldName::new("op");
                                                        NAME.as_str()
                                                    }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::SPAN)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let mut interest = ::tracing::subscriber::Interest::never();
                if ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::LevelFilter::current() &&
                            { interest = __CALLSITE.interest(); !interest.is_never() }
                        &&
                        ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                            interest) {
                    let meta = __CALLSITE.metadata();
                    ::tracing::Span::new(meta,
                        &{
                                #[allow(unused_imports)]
                                use ::tracing::field::{debug, display, Value};
                                meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&op)
                                                            as &dyn ::tracing::field::Value))])
                            })
                } else {
                    let span =
                        ::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
                    {};
                    span
                }
            };
        __tracing_attr_guard = __tracing_attr_span.enter();
    }

    #[warn(clippy :: suspicious_else_formatting)]
    {

        #[allow(unknown_lints, unreachable_code, clippy ::
        diverging_sub_expression, clippy :: empty_loop, clippy ::
        let_unit_value, clippy :: let_with_type_underscore, clippy ::
        needless_return, clippy :: unreachable)]
        if false {
            let __tracing_attr_fake_return: InterpResult<'tcx, VariantIdx> =
                loop {};
            return __tracing_attr_fake_return;
        }
        {
            let ty = op.layout().ty;
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("event /rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs:58",
                                    "rustc_const_eval::interpret::discriminant",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs"),
                                    ::tracing_core::__macro_support::Option::Some(58u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_const_eval::interpret::discriminant"),
                                    ::tracing_core::field::FieldSet::new(&["message"],
                                        ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::EVENT)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let enabled =
                    ::tracing::Level::TRACE <=
                                ::tracing::level_filters::STATIC_MAX_LEVEL &&
                            ::tracing::Level::TRACE <=
                                ::tracing::level_filters::LevelFilter::current() &&
                        {
                            let interest = __CALLSITE.interest();
                            !interest.is_never() &&
                                ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                    interest)
                        };
                if enabled {
                    (|value_set: ::tracing::field::ValueSet|
                                {
                                    let meta = __CALLSITE.metadata();
                                    ::tracing::Event::dispatch(meta, &value_set);
                                    ;
                                })({
                            #[allow(unused_imports)]
                            use ::tracing::field::{debug, display, Value};
                            __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("read_discriminant_value {0:#?}",
                                                                op.layout()) as &dyn ::tracing::field::Value))])
                        });
                } else { ; }
            };
            let discr_layout = self.layout_of(ty.discriminant_ty(*self.tcx))?;
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("event /rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs:61",
                                    "rustc_const_eval::interpret::discriminant",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs"),
                                    ::tracing_core::__macro_support::Option::Some(61u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_const_eval::interpret::discriminant"),
                                    ::tracing_core::field::FieldSet::new(&["message"],
                                        ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::EVENT)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let enabled =
                    ::tracing::Level::TRACE <=
                                ::tracing::level_filters::STATIC_MAX_LEVEL &&
                            ::tracing::Level::TRACE <=
                                ::tracing::level_filters::LevelFilter::current() &&
                        {
                            let interest = __CALLSITE.interest();
                            !interest.is_never() &&
                                ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                    interest)
                        };
                if enabled {
                    (|value_set: ::tracing::field::ValueSet|
                                {
                                    let meta = __CALLSITE.metadata();
                                    ::tracing::Event::dispatch(meta, &value_set);
                                    ;
                                })({
                            #[allow(unused_imports)]
                            use ::tracing::field::{debug, display, Value};
                            __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("discriminant type: {0:?}",
                                                                discr_layout.ty) as &dyn ::tracing::field::Value))])
                        });
                } else { ; }
            };
            let (tag_scalar_layout, tag_encoding, tag_field) =
                match op.layout().variants {
                    Variants::Empty => {
                        do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::UninhabitedEnumVariantRead(None));
                    }
                    Variants::Single { index } => {
                        if op.layout().is_uninhabited() {
                            do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::UninhabitedEnumVariantRead(Some(index)));
                        }
                        return interp_ok(index);
                    }
                    Variants::Multiple { tag, ref tag_encoding, tag_field, .. }
                        => {
                        (tag, tag_encoding, tag_field)
                    }
                };
            let tag_layout =
                self.layout_of(tag_scalar_layout.primitive().to_int_ty(*self.tcx))?;
            let tag_val =
                self.read_immediate(&self.project_field(op, tag_field)?)?;
            {
                match (&tag_layout.size, &tag_val.layout.size) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
            {
                match (&tag_layout.backend_repr.is_signed(),
                        &tag_val.layout.backend_repr.is_signed()) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("event /rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs:103",
                                    "rustc_const_eval::interpret::discriminant",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/compiler/rustc_const_eval/src/interpret/discriminant.rs"),
                                    ::tracing_core::__macro_support::Option::Some(103u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_const_eval::interpret::discriminant"),
                                    ::tracing_core::field::FieldSet::new(&["message"],
                                        ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::EVENT)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let enabled =
                    ::tracing::Level::TRACE <=
                                ::tracing::level_filters::STATIC_MAX_LEVEL &&
                            ::tracing::Level::TRACE <=
                                ::tracing::level_filters::LevelFilter::current() &&
                        {
                            let interest = __CALLSITE.interest();
                            !interest.is_never() &&
                                ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                                    interest)
                        };
                if enabled {
                    (|value_set: ::tracing::field::ValueSet|
                                {
                                    let meta = __CALLSITE.metadata();
                                    ::tracing::Event::dispatch(meta, &value_set);
                                    ;
                                })({
                            #[allow(unused_imports)]
                            use ::tracing::field::{debug, display, Value};
                            __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("tag value: {0}",
                                                                tag_val) as &dyn ::tracing::field::Value))])
                        });
                } else { ; }
            };
            let index =
                match *tag_encoding {
                    TagEncoding::Direct => {
                        let tag_bits =
                            tag_val.to_scalar().try_to_scalar_int().map_err(|dbg_val|
                                            ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(dbg_val)))?.to_bits(tag_layout.size);
                        if !tag_scalar_layout.valid_range(self).contains(tag_bits) {
                            do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(Scalar::from_uint(tag_bits,
                                            tag_layout.size)))
                        }
                        let discr_val =
                            self.int_to_int_or_float(&tag_val, discr_layout).unwrap();
                        let discr_bits =
                            discr_val.to_scalar().to_bits(discr_layout.size)?;
                        let index =
                            match *ty.kind() {
                                        ty::Adt(adt, _) => {
                                            adt.discriminants(*self.tcx).find(|(_, var)|
                                                    var.val == discr_bits)
                                        }
                                        ty::Coroutine(def_id, args) => {
                                            let args = args.as_coroutine();
                                            args.discriminants(def_id,
                                                    *self.tcx).find(|(_, var)| var.val == discr_bits)
                                        }
                                        _ =>
                                            bug_impl(Some(self.cur_span()),
                                                format_args!("tagged layout for non-adt non-coroutine"),
                                                Location::caller()),
                                    }.ok_or_else(||
                                        ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(Scalar::from_uint(tag_bits,
                                                    tag_layout.size))))?;
                        index.0
                    }
                    TagEncoding::Niche {
                        untagged_variant, ref niche_variants, niche_start } => {
                        let tag_val = tag_val.to_scalar();
                        let variants_start = niche_variants.start.as_u32();
                        let variants_last = niche_variants.last.as_u32();
                        let variant =
                            match tag_val.try_to_scalar_int() {
                                Err(dbg_val) => {
                                    let ptr_valid =
                                        niche_start == 0 && variants_start == variants_last &&
                                            !self.scalar_may_be_null(tag_val)?;
                                    if !ptr_valid {
                                        do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(dbg_val))
                                    }
                                    untagged_variant
                                }
                                Ok(tag_bits) => {
                                    let tag_bits = tag_bits.to_bits(tag_layout.size);
                                    let tag_val = ImmTy::from_uint(tag_bits, tag_layout);
                                    let niche_start_val =
                                        ImmTy::from_uint(niche_start, tag_layout);
                                    let variant_index_relative_val =
                                        self.binary_op(mir::BinOp::Sub, &tag_val,
                                                &niche_start_val)?;
                                    let variant_index_relative =
                                        variant_index_relative_val.to_scalar().to_bits(tag_val.layout.size)?;
                                    if variant_index_relative <=
                                            u128::from(variants_last - variants_start) {
                                        let variant_index_relative =
                                            u32::try_from(variant_index_relative).expect("we checked that this fits into a u32");
                                        let variant_index =
                                            VariantIdx::from_u32(variants_start.checked_add(variant_index_relative).expect("overflow computing absolute variant idx"));
                                        let variants =
                                            ty.ty_adt_def().expect("tagged layout for non adt").variants();
                                        if !(variant_index < variants.next_index()) {
                                            ::core::panicking::panic("assertion failed: variant_index < variants.next_index()")
                                        };
                                        if !tag_scalar_layout.valid_range(self).contains(tag_bits) {
                                            ::core::panicking::panic("assertion failed: tag_scalar_layout.valid_range(self).contains(tag_bits)")
                                        };
                                        if variant_index == untagged_variant {
                                            do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(Scalar::from_uint(tag_bits,
                                                            tag_layout.size)))
                                        }
                                        variant_index
                                    } else {
                                        if !tag_scalar_layout.valid_range(self).contains(tag_bits) {
                                            do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::InvalidTag(Scalar::from_uint(tag_bits,
                                                            tag_layout.size)))
                                        }
                                        untagged_variant
                                    }
                                }
                            };
                        variant
                    }
                };
            if op.layout().is_variant_uninhabited(index) {
                do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::UninhabitedEnumVariantRead(Some(index)))
            }
            interp_ok(index)
        }
    }
}#[instrument(skip(self), level = "trace")]
53    pub fn read_discriminant(
54        &self,
55        op: &impl Projectable<'tcx, M::Provenance>,
56    ) -> InterpResult<'tcx, VariantIdx> {
57        let ty = op.layout().ty;
58        trace!("read_discriminant_value {:#?}", op.layout());
59        // Get type and layout of the discriminant.
60        let discr_layout = self.layout_of(ty.discriminant_ty(*self.tcx))?;
61        trace!("discriminant type: {:?}", discr_layout.ty);
62
63        // We use "discriminant" to refer to the value associated with a particular enum variant.
64        // This is not to be confused with its "variant index", which is just determining its position in the
65        // declared list of variants -- they can differ with explicitly assigned discriminants.
66        // We use "tag" to refer to how the discriminant is encoded in memory, which can be either
67        // straight-forward (`TagEncoding::Direct`) or with a niche (`TagEncoding::Niche`).
68        let (tag_scalar_layout, tag_encoding, tag_field) = match op.layout().variants {
69            Variants::Empty => {
70                throw_ub!(UninhabitedEnumVariantRead(None));
71            }
72            Variants::Single { index } => {
73                if op.layout().is_uninhabited() {
74                    // For consistency with `write_discriminant`, and to make sure that
75                    // `project_downcast` cannot fail due to strange layouts, we declare immediate UB
76                    // for uninhabited enums.
77                    throw_ub!(UninhabitedEnumVariantRead(Some(index)));
78                }
79                // Since the type is inhabited, there must be an index.
80                return interp_ok(index);
81            }
82            Variants::Multiple { tag, ref tag_encoding, tag_field, .. } => {
83                (tag, tag_encoding, tag_field)
84            }
85        };
86
87        // There are *three* layouts that come into play here:
88        // - The discriminant has a type for typechecking. This is `discr_layout`, and is used for
89        //   the `Scalar` we return.
90        // - The tag (encoded discriminant) has layout `tag_layout`. This is always an integer type,
91        //   and used to interpret the value we read from the tag field.
92        //   For the return value, a cast to `discr_layout` is performed.
93        // - The field storing the tag has a layout, which is very similar to `tag_layout` but
94        //   may be a pointer. This is `tag_val.layout`; we just use it for sanity checks.
95
96        // Get layout for tag.
97        let tag_layout = self.layout_of(tag_scalar_layout.primitive().to_int_ty(*self.tcx))?;
98
99        // Read tag and sanity-check `tag_layout`.
100        let tag_val = self.read_immediate(&self.project_field(op, tag_field)?)?;
101        assert_eq!(tag_layout.size, tag_val.layout.size);
102        assert_eq!(tag_layout.backend_repr.is_signed(), tag_val.layout.backend_repr.is_signed());
103        trace!("tag value: {}", tag_val);
104
105        // Figure out which discriminant and variant this corresponds to.
106        let index = match *tag_encoding {
107            TagEncoding::Direct => {
108                // Generate a specific error if `tag_val` is not an integer.
109                // (`tag_bits` itself is only used for error messages below.)
110                let tag_bits = tag_val
111                    .to_scalar()
112                    .try_to_scalar_int()
113                    .map_err(|dbg_val| err_ub!(InvalidTag(dbg_val)))?
114                    .to_bits(tag_layout.size);
115                // Ensure the tag is in its layout range. Codegen adds range metadata on the
116                // discriminant load so we really have to make this UB.
117                if !tag_scalar_layout.valid_range(self).contains(tag_bits) {
118                    throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size)))
119                }
120                // Cast bits from tag layout to discriminant layout.
121                // After the checks we did above, this cannot fail, as
122                // discriminants are int-like.
123                let discr_val = self.int_to_int_or_float(&tag_val, discr_layout).unwrap();
124                let discr_bits = discr_val.to_scalar().to_bits(discr_layout.size)?;
125                // Convert discriminant to variant index. The tag may pass the layout range
126                // check above but still not match any actual variant discriminant (e.g.,
127                // non-contiguous discriminants with a wrapping valid_range).
128                let index = match *ty.kind() {
129                    ty::Adt(adt, _) => {
130                        adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits)
131                    }
132                    ty::Coroutine(def_id, args) => {
133                        let args = args.as_coroutine();
134                        args.discriminants(def_id, *self.tcx).find(|(_, var)| var.val == discr_bits)
135                    }
136                    _ => span_bug!(self.cur_span(), "tagged layout for non-adt non-coroutine"),
137                }
138                .ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?;
139                // Return the cast value, and the index.
140                index.0
141            }
142            TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => {
143                let tag_val = tag_val.to_scalar();
144                // Compute the variant this niche value/"tag" corresponds to. With niche layout,
145                // discriminant (encoded in niche/tag) and variant index are the same.
146                let variants_start = niche_variants.start.as_u32();
147                let variants_last = niche_variants.last.as_u32();
148                let variant = match tag_val.try_to_scalar_int() {
149                    Err(dbg_val) => {
150                        // So this is a pointer then, and casting to an int failed.
151                        // Can only happen during CTFE.
152                        // The niche must be just 0, and the ptr not null, then we know this is
153                        // okay. Everything else, we conservatively reject.
154                        let ptr_valid = niche_start == 0
155                            && variants_start == variants_last
156                            && !self.scalar_may_be_null(tag_val)?;
157                        if !ptr_valid {
158                            throw_ub!(InvalidTag(dbg_val))
159                        }
160                        untagged_variant
161                    }
162                    Ok(tag_bits) => {
163                        let tag_bits = tag_bits.to_bits(tag_layout.size);
164                        // We need to use machine arithmetic to get the relative variant idx:
165                        // variant_index_relative = tag_val - niche_start_val
166                        let tag_val = ImmTy::from_uint(tag_bits, tag_layout);
167                        let niche_start_val = ImmTy::from_uint(niche_start, tag_layout);
168                        let variant_index_relative_val =
169                            self.binary_op(mir::BinOp::Sub, &tag_val, &niche_start_val)?;
170                        let variant_index_relative =
171                            variant_index_relative_val.to_scalar().to_bits(tag_val.layout.size)?;
172                        // Check if this is in the range that indicates an actual discriminant.
173                        if variant_index_relative <= u128::from(variants_last - variants_start) {
174                            let variant_index_relative = u32::try_from(variant_index_relative)
175                                .expect("we checked that this fits into a u32");
176                            // Then computing the absolute variant idx should not overflow any more.
177                            let variant_index = VariantIdx::from_u32(
178                                variants_start
179                                    .checked_add(variant_index_relative)
180                                    .expect("overflow computing absolute variant idx"),
181                            );
182                            let variants =
183                                ty.ty_adt_def().expect("tagged layout for non adt").variants();
184                            assert!(variant_index < variants.next_index());
185                            // This should imply that the tag is in its layout range.
186                            assert!(tag_scalar_layout.valid_range(self).contains(tag_bits));
187
188                            if variant_index == untagged_variant {
189                                // The untagged variant can be in the niche range, but even then it
190                                // is not a valid encoding. Codegen inserts an `assume` here
191                                // so we really have to make this UB.
192                                throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size)))
193                            }
194                            variant_index
195                        } else {
196                            // Ensure the tag is in its layout range. Codegen adds range metadata on
197                            // the discriminant load so we really have to make this UB.
198                            if !tag_scalar_layout.valid_range(self).contains(tag_bits) {
199                                throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size)))
200                            }
201                            untagged_variant
202                        }
203                    }
204                };
205                // Compute the size of the scalar we need to return.
206                // No need to cast, because the variant index directly serves as discriminant and is
207                // encoded in the tag.
208                variant
209            }
210        };
211        // Reading the discriminant of an uninhabited variant is UB. This is the basis for the
212        // `uninhabited_enum_branching` MIR pass. It also ensures consistency with
213        // `write_discriminant`.
214        if op.layout().is_variant_uninhabited(index) {
215            throw_ub!(UninhabitedEnumVariantRead(Some(index)))
216        }
217        interp_ok(index)
218    }
219
220    /// Read discriminant, return the user-visible discriminant.
221    /// Can also legally be called on non-enums (e.g. through the discriminant_value intrinsic)!
222    pub fn discriminant_for_variant(
223        &self,
224        ty: Ty<'tcx>,
225        variant: VariantIdx,
226    ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
227        let discr_layout = self.layout_of(ty.discriminant_ty(*self.tcx))?;
228        let discr_value = match ty.discriminant_for_variant(*self.tcx, variant) {
229            Some(discr) => {
230                // This type actually has discriminants.
231                {
    match (&discr.ty, &discr_layout.ty) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(discr.ty, discr_layout.ty);
232                Scalar::from_uint(discr.val, discr_layout.size)
233            }
234            None => {
235                // On a type without actual discriminants, variant is 0.
236                {
    match (&variant.as_u32(), &0) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(variant.as_u32(), 0);
237                Scalar::from_uint(variant.as_u32(), discr_layout.size)
238            }
239        };
240        interp_ok(ImmTy::from_scalar(discr_value, discr_layout))
241    }
242
243    /// Computes how to write the tag of a given variant of enum `ty`:
244    /// - `None` means that nothing needs to be done as the variant is encoded implicitly
245    /// - `Some((val, field_idx))` means that the given integer value needs to be stored at the
246    ///   given field index.
247    pub(crate) fn tag_for_variant(
248        &self,
249        layout: TyAndLayout<'tcx>,
250        variant_index: VariantIdx,
251    ) -> InterpResult<'tcx, Option<(ScalarInt, FieldIdx)>> {
252        // Layout computation excludes uninhabited variants from consideration.
253        // Therefore, there's no way to represent those variants in the given layout.
254        // Essentially, uninhabited variants do not have a tag that corresponds to their
255        // discriminant, so we have to bail out here.
256        if layout.is_variant_uninhabited(variant_index) {
257            do yeet ::rustc_middle::mir::interpret::InterpErrorKind::UndefinedBehavior(::rustc_middle::mir::interpret::UndefinedBehaviorInfo::UninhabitedEnumVariantWritten(variant_index))throw_ub!(UninhabitedEnumVariantWritten(variant_index))
258        }
259
260        match layout.variants {
261            abi::Variants::Empty => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("we already handled uninhabited types")));
}unreachable!("we already handled uninhabited types"),
262            abi::Variants::Single { .. } => {
263                // The tag of a `Single` enum is like the tag of the niched
264                // variant: there's no tag as the discriminant is encoded
265                // entirely implicitly. If `write_discriminant` ever hits this
266                // case, we do a "validation read" to ensure the right
267                // discriminant is encoded implicitly, so any attempt to write
268                // the wrong discriminant for a `Single` enum will reliably
269                // result in UB.
270                interp_ok(None)
271            }
272
273            abi::Variants::Multiple {
274                tag_encoding: TagEncoding::Direct,
275                tag: tag_layout,
276                tag_field,
277                ..
278            } => {
279                // raw discriminants for enums are isize or bigger during
280                // their computation, but the in-memory tag is the smallest possible
281                // representation
282                let discr = self.discriminant_for_variant(layout.ty, variant_index)?;
283                let discr_size = discr.layout.size;
284                let discr_val = discr.to_scalar().to_bits(discr_size)?;
285                let tag_size = tag_layout.size(self);
286                let tag_val = tag_size.truncate(discr_val);
287                let tag = ScalarInt::try_from_uint(tag_val, tag_size).unwrap();
288                interp_ok(Some((tag, tag_field)))
289            }
290
291            abi::Variants::Multiple {
292                tag_encoding: TagEncoding::Niche { untagged_variant, .. },
293                ..
294            } if untagged_variant == variant_index => {
295                // The untagged variant is implicitly encoded simply by having a
296                // value that is outside the niche variants.
297                interp_ok(None)
298            }
299
300            abi::Variants::Multiple {
301                tag_encoding:
302                    TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
303                tag: tag_layout,
304                tag_field,
305                ..
306            } => {
307                if !(variant_index != untagged_variant) {
    ::core::panicking::panic("assertion failed: variant_index != untagged_variant")
};assert!(variant_index != untagged_variant);
308                // We checked that this variant is inhabited, so it must be in the niche range.
309                if !niche_variants.contains(&variant_index) {
    {
        ::core::panicking::panic_fmt(format_args!("invalid variant index for this enum"));
    }
};assert!(
310                    niche_variants.contains(&variant_index),
311                    "invalid variant index for this enum"
312                );
313                let variants_start = niche_variants.start.as_u32();
314                let variant_index_relative = variant_index.as_u32().strict_sub(variants_start);
315                // We need to use machine arithmetic when taking into account `niche_start`:
316                // tag_val = variant_index_relative + niche_start_val
317                let tag_layout = self.layout_of(tag_layout.primitive().to_int_ty(*self.tcx))?;
318                let niche_start_val = ImmTy::from_uint(niche_start, tag_layout);
319                let variant_index_relative_val =
320                    ImmTy::from_uint(variant_index_relative, tag_layout);
321                let tag = self
322                    .binary_op(mir::BinOp::Add, &variant_index_relative_val, &niche_start_val)?
323                    .to_scalar_int()?;
324                interp_ok(Some((tag, tag_field)))
325            }
326        }
327    }
328}