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. --ICE is triggered when building in file in release mode. I'm not able to reproduce when building in debug mode. In my situation, the crate is built as part of the dependency tree for a binary crate. <!-- 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
cc this seems like those mir-inliner bugs :thinking:
Bisected to which is likely (similar as issues and ).
Saw a couple of these in Crater, though typically with some other ICEs too (which is why I didn't file bugs). But tagging as 1.64 and beta regression.
For the record, that PR probably revealed this bug but is likely not the cause of it.
Indeed, that's the "usual" normalization failure ICE we have with the MIR inliner. When enabling the MIR inliner, I only a check that we don't have such type mismatch at the signature, but I did not attempt to check all types that appear in the MIR for such mismatch. If we want to fully avoid this ICE, we'd need to fully visit the callee body to check for those type mismatches. do you know if progress has been made on the normalization front?
Hmm, maybe this is fixed by , but I don't have time to investigate this right now. Edit: It's not fixed.
Do you think it may make sense to disable the MIR inliner on beta to give it some more time to bake? I think it was switched on mid-cycle in 1.64, right?
I qualify this as a bug in type normalization that MIR validation exposes. It's not a bug in the MIR inliner. There are 3 ways to address this: more exceptions to the MIR inliner to avoid those bugs (easy but cumbersome, ); type normalization (very hard); MIR validation (tentative in ); validating MIR in released compilers but keep doing it on tests (very easy). However, I don't think that disabling the MIR inliner will help.
Stopping to validate MIR sounds like a really bad idea to me. MIR validation (in released compilers) has caught many critical issues already like (I found it while investigating a MIR validation failure that someone found in the wild because the mir typeck found something typeck let through) and the most recent let_chains problem which was also found because of a MIR validation ICE.
WG-prioritization assigning priority (). label -I-prioritize +P-critical
Reduced example: label S-bug-has-mcve
Weird, this one crashes with but I couldn't get this to trigger with just mir-opt-levels :thinking:

Discussion:
// Normalize lifetimes away on both sides, then compare. let normalize = |ty: Ty<'tcx>| { <del> tcx.normalize_erasing_regions( param_env, ty.fold_with(&mut BottomUpFolder { tcx, // FIXME: We erase all late-bound lifetimes, but this is not fully correct. // If you have a type like `<for<'a> fn(&'a u32) as SomeTrait>::Assoc`, // this is not necessarily equivalent to `<fn(&'static u32) as SomeTrait>::Assoc`, // since one may have an `impl SomeTrait for fn(&32)` and // `impl SomeTrait for fn(&'static u32)` at the same time which // specify distinct values for Assoc. (See also #56105) lt_op: |_| tcx.lifetimes.re_erased, // Leave consts and types unchanged. ct_op: |ct| ct, ty_op: |ty| ty, }), ) </del> <ins> let ty = ty.fold_with(&mut BottomUpFolder { tcx, // FIXME: We erase all late-bound lifetimes, but this is not fully correct. // If you have a type like `<for<'a> fn(&'a u32) as SomeTrait>::Assoc`, // this is not necessarily equivalent to `<fn(&'static u32) as SomeTrait>::Assoc`, // since one may have an `impl SomeTrait for fn(&32)` and // `impl SomeTrait for fn(&'static u32)` at the same time which // specify distinct values for Assoc. (See also #56105) lt_op: |_| tcx.lifetimes.re_erased, // Leave consts and types unchanged. ct_op: |ct| ct, ty_op: |ty| ty, }); tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty) </ins> }; tcx.infer_ctxt().enter(|infcx| infcx.can_eq(param_env, normalize(src), normalize(dest)).is_ok()) }

use rustc_middle::ty::{self, ConstKind, Instance, InstanceDef, ParamEnv, Ty, TyCtxt}; use rustc_session::config::OptLevel; use rustc_span::{hygiene::ExpnKind, ExpnData, LocalExpnId, Span}; <ins> use rustc_target::abi::VariantIdx; </ins> use rustc_target::spec::abi::Abi; use super::simplify::{remove_dead_blocks, CfgSimplifier};

debug!("    final inline threshold = {}", threshold); // FIXME: Give a bonus to functions with only a single caller <del> let mut first_block = true; let mut cost = 0; </del> <ins> let diverges = matches!( callee_body.basic_blocks()[START_BLOCK].terminator().kind, TerminatorKind::Unreachable | TerminatorKind::Call { target: None, .. } ); if diverges && !matches!(callee_attrs.inline, InlineAttr::Always) { return Err("callee diverges unconditionally"); } let mut checker = CostChecker { tcx: self.tcx, param_env: self.param_env, instance: callsite.callee, callee_body, cost: 0, validation: Ok(()), }; </ins> <del> // Traverse the MIR manually so we can account for the effects of // inlining on the CFG. </del> <ins> // Traverse the MIR manually so we can account for the effects of inlining on the CFG. </ins> let mut work_list = vec![START_BLOCK]; let mut visited = BitSet::new_empty(callee_body.basic_blocks().len()); while let Some(bb) = work_list.pop() { if !visited.insert(bb.index()) { continue; } let blk = &callee_body.basic_blocks()[bb]; <ins> checker.visit_basic_block_data(bb, blk); </ins> <del> for stmt in &blk.statements { // Don't count StorageLive/StorageDead in the inlining cost. match stmt.kind { StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Deinit(_) | StatementKind::Nop => {} _ => cost += INSTR_COST, } } </del> let term = blk.terminator(); <del> let mut is_drop = false; match term.kind { TerminatorKind::Drop { ref place, target, unwind } | TerminatorKind::DropAndReplace { ref place, target, unwind, .. } => { is_drop = true; work_list.push(target); // If the place doesn't actually need dropping, treat it like // a regular goto. let ty = callsite.callee.subst_mir(self.tcx, &place.ty(callee_body, tcx).ty); if ty.needs_drop(tcx, self.param_env) { cost += CALL_PENALTY; if let Some(unwind) = unwind { cost += LANDINGPAD_PENALTY; work_list.push(unwind); } } else { cost += INSTR_COST; } } TerminatorKind::Unreachable | TerminatorKind::Call { target: None, .. } if first_block => { // If the function always diverges, don't inline // unless the cost is zero threshold = 0; } TerminatorKind::Call { func: Operand::Constant(ref f), cleanup, .. } => { if let ty::FnDef(def_id, _) = *callsite.callee.subst_mir(self.tcx, &f.literal.ty()).kind() { // Don't give intrinsics the extra penalty for calls if tcx.is_intrinsic(def_id) { cost += INSTR_COST; } else { cost += CALL_PENALTY; } } else { cost += CALL_PENALTY; } if cleanup.is_some() { cost += LANDINGPAD_PENALTY; } } TerminatorKind::Assert { cleanup, .. } => { cost += CALL_PENALTY; if cleanup.is_some() { cost += LANDINGPAD_PENALTY; } } TerminatorKind::Resume => cost += RESUME_PENALTY, TerminatorKind::InlineAsm { cleanup, .. } => { cost += INSTR_COST; </del> <ins> if let TerminatorKind::Drop { ref place, target, unwind } | TerminatorKind::DropAndReplace { ref place, target, unwind, .. } = term.kind { work_list.push(target); </ins> <del> if cleanup.is_some() { cost += LANDINGPAD_PENALTY; </del> <ins> // If the place doesn't actually need dropping, treat it like a regular goto. let ty = callsite.callee.subst_mir(self.tcx, &place.ty(callee_body, tcx).ty); if ty.needs_drop(tcx, self.param_env) && let Some(unwind) = unwind { work_list.push(unwind); </ins> } <del> } _ => cost += INSTR_COST, } if !is_drop { for succ in term.successors() { work_list.push(succ); } </del> <ins> } else { work_list.extend(term.successors()) </ins> } <del> first_block = false; </del> } // Count up the cost of local variables and temps, if we know the size // use that, otherwise we use a moderately-large dummy cost. <del> let ptr_size = tcx.data_layout.pointer_size.bytes(); </del> for v in callee_body.vars_and_temps_iter() { <del> let ty = callsite.callee.subst_mir(self.tcx, &callee_body.local_decls[v].ty); // Cost of the var is the size in machine-words, if we know // it. if let Some(size) = type_size_of(tcx, self.param_env, ty) { cost += ((size + ptr_size - 1) / ptr_size) as usize; } else { cost += UNKNOWN_SIZE_COST; } </del> <ins> checker.visit_local_decl(v, &callee_body.local_decls[v]); </ins> } <ins> // Abort if type validation found anything fishy. checker.validation?; let cost = checker.cost; </ins> if let InlineAttr::Always = callee_attrs.inline { debug!("INLINING {:?} because inline(always) [cost={}]", callsite, cost); Ok(())

tcx.layout_of(param_env.and(ty)).ok().map(|layout| layout.size.bytes()) } <ins> /// Verify that the callee body is compatible with the caller. /// /// This visitor mostly computes the inlining cost, /// but also needs to verify that types match because of normalization failure. struct CostChecker<'b, 'tcx> { tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, cost: usize, callee_body: &'b Body<'tcx>, instance: ty::Instance<'tcx>, validation: Result<(), &'static str>, } impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { // Don't count StorageLive/StorageDead in the inlining cost. match statement.kind { StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Deinit(_) | StatementKind::Nop => {} _ => self.cost += INSTR_COST, } self.super_statement(statement, location); } fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { let tcx = self.tcx; match terminator.kind { TerminatorKind::Drop { ref place, unwind, .. } | TerminatorKind::DropAndReplace { ref place, unwind, .. } => { // If the place doesn't actually need dropping, treat it like a regular goto. let ty = self.instance.subst_mir(tcx, &place.ty(self.callee_body, tcx).ty); if ty.needs_drop(tcx, self.param_env) { self.cost += CALL_PENALTY; if unwind.is_some() { self.cost += LANDINGPAD_PENALTY; } } else { self.cost += INSTR_COST; } } TerminatorKind::Call { func: Operand::Constant(ref f), cleanup, .. } => { let fn_ty = self.instance.subst_mir(tcx, &f.literal.ty()); self.cost += if let ty::FnDef(def_id, _) = *fn_ty.kind() && tcx.is_intrinsic(def_id) { // Don't give intrinsics the extra penalty for calls INSTR_COST } else { CALL_PENALTY }; if cleanup.is_some() { self.cost += LANDINGPAD_PENALTY; } } TerminatorKind::Assert { cleanup, .. } => { self.cost += CALL_PENALTY; if cleanup.is_some() { self.cost += LANDINGPAD_PENALTY; } } TerminatorKind::Resume => self.cost += RESUME_PENALTY, TerminatorKind::InlineAsm { cleanup, .. } => { self.cost += INSTR_COST; if cleanup.is_some() { self.cost += LANDINGPAD_PENALTY; } } _ => self.cost += INSTR_COST, } self.super_terminator(terminator, location); } /// Count up the cost of local variables and temps, if we know the size /// use that, otherwise we use a moderately-large dummy cost. fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) { let tcx = self.tcx; let ptr_size = tcx.data_layout.pointer_size.bytes(); let ty = self.instance.subst_mir(tcx, &local_decl.ty); // Cost of the var is the size in machine-words, if we know // it. if let Some(size) = type_size_of(tcx, self.param_env, ty) { self.cost += ((size + ptr_size - 1) / ptr_size) as usize; } else { self.cost += UNKNOWN_SIZE_COST; } self.super_local_decl(local, local_decl) } /// This method duplicates code from MIR validation in an attempt to detect type mismatches due /// to normalization failure. fn visit_projection_elem( &mut self, local: Local, proj_base: &[PlaceElem<'tcx>], elem: PlaceElem<'tcx>, context: PlaceContext, location: Location, ) { if let ProjectionElem::Field(f, ty) = elem { let parent = Place { local, projection: self.tcx.intern_place_elems(proj_base) }; let parent_ty = parent.ty(&self.callee_body.local_decls, self.tcx); let check_equal = |this: &mut Self, f_ty| { if !equal_up_to_regions(this.tcx, this.param_env, ty, f_ty) { trace!(?ty, ?f_ty); this.validation = Err("failed to normalize projection type"); return; } }; let kind = match parent_ty.ty.kind() { &ty::Opaque(def_id, substs) => { self.tcx.bound_type_of(def_id).subst(self.tcx, substs).kind() } kind => kind, }; match kind { ty::Tuple(fields) => { let Some(f_ty) = fields.get(f.as_usize()) else { self.validation = Err("malformed MIR"); return; }; check_equal(self, *f_ty); } ty::Adt(adt_def, substs) => { let var = parent_ty.variant_index.unwrap_or(VariantIdx::from_u32(0)); let Some(field) = adt_def.variant(var).fields.get(f.as_usize()) else { self.validation = Err("malformed MIR"); return; }; check_equal(self, field.ty(self.tcx, substs)); } ty::Closure(_, substs) => { let substs = substs.as_closure(); let Some(f_ty) = substs.upvar_tys().nth(f.as_usize()) else { self.validation = Err("malformed MIR"); return; }; check_equal(self, f_ty); } &ty::Generator(def_id, substs, _) => { let f_ty = if let Some(var) = parent_ty.variant_index { let gen_body = if def_id == self.callee_body.source.def_id() { self.callee_body } else { self.tcx.optimized_mir(def_id) }; let Some(layout) = gen_body.generator_layout() else { self.validation = Err("malformed MIR"); return; }; let Some(&local) = layout.variant_fields[var].get(f) else { self.validation = Err("malformed MIR"); return; }; let Some(&f_ty) = layout.field_tys.get(local) else { self.validation = Err("malformed MIR"); return; }; f_ty } else { let Some(f_ty) = substs.as_generator().prefix_tys().nth(f.index()) else { self.validation = Err("malformed MIR"); return; }; f_ty }; check_equal(self, f_ty); } _ => self.validation = Err("malformed MIR"), } } self.super_projection_elem(local, proj_base, elem, context, location); } } </ins> /** * Integrator. *

<ins> // This test verifies that we do not ICE due to MIR inlining in case of normalization failure // in a projection. // // compile-flags: --crate-type lib -C opt-level=3 // build-pass pub trait Trait { type Associated; } impl<T> Trait for T { type Associated = T; } pub struct Struct<T>(<T as Trait>::Associated); pub fn foo<T>() -> Struct<T> where T: Trait, { bar() } #[inline] fn bar<T>() -> Struct<T> { Struct(baz()) } fn baz<T>() -> T { unimplemented!() } </ins>