Change:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
For the record, it is due to and doesn't require (but does require that flag, and only happens during ) I've tracked this down (possibly) to which is only enabled when opt-level = 3. I might take a look at this..
So actually this doesn't even depend on . I'm not sure which mir pass is causing this actually, but only happens when we match which is inlined in a match block (not in a separate statement). The variable binding for the case is also important, and the issue seems to be a discriminant value of being produced somewhere... minimized (ICEs with ):
Yeah so it just seems like we're creating invalid scalar values as intermediates in optimization passes (e.g. after const propagation), and then panicking when we pretty-print those invalid values when dumping MIR. I can give a more detailed explanation why we're creating an invalid scalar, but that scalar is being optimized out anyways in subsequent optimization passes, so I don't think this would ever affect codegen... So not sure how scary this ICE is in practice. I would claim this issue, but I don't know what the best fix for this would be. Perhaps making return a instead of a value...? Not sure who to ask -- or perhaps either of y'all have an opinion on this? <details<summary<breason why we're creating an invalid scalar</b</summaryvalue is const-evaluated into the scalar value . we generate the MIR branch for , we emit the MIR expression where is that scalar value above. const-propagation, we try to inline (that is, scalar value ) into all places that reference it, even those that are invalid, such as the unreachable branch. , we end up basically calculating . This ends up creating an with an invalid scalar value of (we only expect the values and ). then try to pretty print that value by getting its discriminant and values, but we are unable to tell what the discriminant of an with an invalid scalar value is. So we ICE. </details
An optimization pass that introduces invalid scalars literally introduces UB into well-defined code (unless this is in dead code), so -- I would say this is rather scary, on the level of "I-unsound".
Fairly certain this only happens in unreachable branches (default branches of exhaustive switches), but i'll dig into it.
Sorry, yes, it only seems to happen (as far as I know) in dead branches. The issue here only seems to be printing MIR with these dead branches still in the code.
Okay that is less critical then. Arguably printing should work even for invalid code. It still seems interesting to figure out why this happens. I would understand if this was an uninhabited variant, but seeing an entirely non-existing discriminant is surprising.
I left an explanation above in an collapsible block as to why we're creating this invalid value. I believe the root cause is that we perform operations in const propagation infallibly. It would probably be desirable to prevent this by making const propagation fallible if the const is incompatible with the layout of the result of the operation being applied to it. Then when we apply something like when to access the field of the variant on a dead branch, we don't create an invalid value.
Okay so it looks like possibly we should adjust the "downcast" projection(s) to verify that the place has the right variant, and otherwise? Then evaluating would fail and ConstProp would not touch that code, I think.
yup that sounds correct. do you want me to make that change?
One thing I am wondering about is if this will work correctly for generators -- the have more complicated variants than enums do, and maybe there are situations there where projections to the "wrong" variant are actually okay? Cc
are you proposing to declare downcast to an inactive variant to be undefined behaviour? This is probably larger change, in a sense that it would be incompatible with how downcast is currently used: In generators each field is accessed through a single representative variant, regardless of the current variant. Aggregate enum assignments are lowered to a field-wise assignments (using downcast) with a discriminant set last.
Yes, basically. But it seems like that plan won't work. Interesting. I should probably add a comment in the code explaining why this is not currently considered UB. Well then it seems the best we can do for this ICE is to make the printing code more robust. It is anyway a good idea to be able to print invalid values; otherwise, debugging invalid values can be quite frustrating.

Discussion:
use rustc_span::{source_map::DUMMY_SP, symbol::Symbol}; use crate::interpret::{ <del> intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MPlaceTy, MemPlaceMeta, Scalar, </del> <ins> intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MPlaceTy, MemPlaceMeta, Scalar, </ins> }; mod error;

} } <del> /// This function uses `unwrap` copiously, because an already validated constant /// must have valid fields and can thus never fail outside of compiler bugs. However, it is /// invoked from the pretty printer, where it can receive enums with no variants and e.g. /// `read_discriminant` needs to be able to handle that. pub(crate) fn destructure_const<'tcx>( </del> <ins> /// This function should never fail for validated constants. However, it is also invoked from the /// pretty printer which might attempt to format invalid constants and in that case it might fail. pub(crate) fn try_destructure_const<'tcx>( </ins> tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, val: ty::Const<'tcx>, <del> ) -> mir::DestructuredConst<'tcx> { </del> <ins> ) -> InterpResult<'tcx, mir::DestructuredConst<'tcx>> { </ins> trace!("destructure_const: {:?}", val); let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); <del> let op = ecx.const_to_op(val, None).unwrap(); </del> <ins> let op = ecx.const_to_op(val, None)?; </ins> // We go to `usize` as we cannot allocate anything bigger anyway. let (field_count, variant, down) = match val.ty().kind() { ty::Array(_, len) => (usize::try_from(len.eval_usize(tcx, param_env)).unwrap(), None, op), <del> ty::Adt(def, _) if def.variants.is_empty() => { return mir::DestructuredConst { variant: None, fields: &[] }; } </del> ty::Adt(def, _) => { <del> let variant = ecx.read_discriminant(&op).unwrap().1; let down = ecx.operand_downcast(&op, variant).unwrap(); </del> <ins> let variant = ecx.read_discriminant(&op)?.1; let down = ecx.operand_downcast(&op, variant)?; </ins> (def.variants[variant].fields.len(), Some(variant), down) } ty::Tuple(substs) => (substs.len(), None, op), _ => bug!("cannot destructure constant {:?}", val), }; <del> let fields_iter = (0..field_count).map(|i| { let field_op = ecx.operand_field(&down, i).unwrap(); let val = op_to_const(&ecx, &field_op); ty::Const::from_value(tcx, val, field_op.layout.ty) }); let fields = tcx.arena.alloc_from_iter(fields_iter); </del> <ins> let fields = (0..field_count) .map(|i| { let field_op = ecx.operand_field(&down, i)?; let val = op_to_const(&ecx, &field_op); Ok(ty::Const::from_value(tcx, val, field_op.layout.ty)) }) .collect::<InterpResult<'tcx, Vec<_>>>()?; let fields = tcx.arena.alloc_from_iter(fields); </ins> <del> mir::DestructuredConst { variant, fields } </del> <ins> Ok(mir::DestructuredConst { variant, fields }) </ins> } pub(crate) fn deref_const<'tcx>(

providers.eval_to_const_value_raw = const_eval::eval_to_const_value_raw_provider; providers.eval_to_allocation_raw = const_eval::eval_to_allocation_raw_provider; providers.const_caller_location = const_eval::const_caller_location; <del> providers.destructure_const = |tcx, param_env_and_value| { </del> <ins> providers.try_destructure_const = |tcx, param_env_and_value| { </ins> let (param_env, value) = param_env_and_value.into_parts(); <del> const_eval::destructure_const(tcx, param_env, value) </del> <ins> const_eval::try_destructure_const(tcx, param_env, value).ok() </ins> }; providers.const_to_valtree = |tcx, param_env_and_value| { let (param_env, raw) = param_env_and_value.into_parts();

let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?; Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory()) } <ins> /// Destructure a constant ADT or array into its variant index and its field values. pub fn destructure_const( self, param_env_and_val: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>>, ) -> mir::DestructuredConst<'tcx> { self.try_destructure_const(param_env_and_val).unwrap() } </ins> }

} /// Destructure a constant ADT or array into its variant index and its <del> /// field values. query destructure_const( </del> <ins> /// field values or return `None` if constant is invalid. /// /// Use infallible `TyCtxt::destructure_const` when you know that constant is valid. query try_destructure_const( </ins> key: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>> <del> ) -> mir::DestructuredConst<'tcx> { </del> <ins> ) -> Option<mir::DestructuredConst<'tcx>> { </ins> desc { "destructure constant" } remap_env_constness }

// FIXME(eddyb) for `--emit=mir`/`-Z dump-mir`, we should provide the // correct `ty::ParamEnv` to allow printing *all* constant values. (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_param_types_or_consts() => { <del> let contents = self.tcx().destructure_const(ty::ParamEnv::reveal_all().and( self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty }), )); </del> <ins> let Some(contents) = self.tcx().try_destructure_const( ty::ParamEnv::reveal_all() .and(self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty })), ) else { // Fall back to debug pretty printing for invalid constants. p!(write("{:?}", ct)); if print_ty { p!(": ", print(ty)); } return Ok(self); }; </ins> let fields = contents.fields.iter().copied(); match *ty.kind() {

<ins> - // MIR for `main` before ConstProp + // MIR for `main` after ConstProp fn main() -> () { let mut _0: ();                      // return place in scope 0 at $DIR/invalid_constant.rs:15:11: 15:11 let _1: std::option::Option<()>;     // in scope 0 at $DIR/invalid_constant.rs:16:5: 16:12 let mut _2: std::option::Option<std::option::Option<()>>; // in scope 0 at $DIR/invalid_constant.rs:16:7: 16:11 scope 1 (inlined f) {                // at $DIR/invalid_constant.rs:16:5: 16:12 debug x => _2;                   // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 let mut _3: isize;               // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 let _4: std::option::Option<()>; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 scope 2 { debug y => _4;               // in scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 } } bb0: { discriminant(_2) = 0;            // scope 0 at $DIR/invalid_constant.rs:16:7: 16:11 -         _3 = discriminant(_2);           // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 -         switchInt(move _3) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 +         _3 = const 0_isize;              // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 +         switchInt(const 0_isize) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 } bb1: { nop;                             // scope 0 at $DIR/invalid_constant.rs:15:11: 17:2 return;                          // scope 0 at $DIR/invalid_constant.rs:17:2: 17:2 } bb2: { -         _4 = ((_2 as Some).0: std::option::Option<()>); // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 -         _1 = _4;                         // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 +         _4 = const Scalar(0x02): Option::<()>; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 +                                          // ty::Const +                                          // + ty: std::option::Option<()> +                                          // + val: Value(Scalar(0x02)) +                                          // mir::Constant +                                          // + span: $DIR/invalid_constant.rs:16:5: 16:12 +                                          // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) } +         _1 = const Scalar(0x02): Option::<()>; // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12 +                                          // ty::Const +                                          // + ty: std::option::Option<()> +                                          // + val: Value(Scalar(0x02)) +                                          // mir::Constant +                                          // + span: $DIR/invalid_constant.rs:16:5: 16:12 +                                          // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) } goto -> bb1;                     // scope 0 at $DIR/invalid_constant.rs:10:20: 10:21 } bb3: { discriminant(_1) = 0;            // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12 goto -> bb1;                     // scope 0 at $DIR/invalid_constant.rs:9:17: 9:21 } } </ins>

<ins> // Verify that we can pretty print invalid constant introduced // by constant propagation. Regression test for issue #93688. // // compile-flags: -Copt-level=0 -Zinline-mir #[inline(always)] pub fn f(x: Option<Option<()>>) -> Option<()> { match x { None => None, Some(y) => y, } } // EMIT_MIR invalid_constant.main.ConstProp.diff fn main() { f(None); } </ins>