Change:
<!-- [31mICE[0m: Rustc '-Cinstrument-coverage -ooutputfile -Zdump-mir-dir=dir' 'thread 'rustc' panicked at 'attempt to add a to a BCB target with existing incoming edge counters'', 'thread 'rustc' panicked at 'attempt to add a to a BCB target with existing incoming edge counters'' File: --auto-reduced (treereduce-rust): original: Version information ` Command: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongProgram output</strong</summary<p</p</details<!-- query stack: [mirpromoted] promoting constants in MIR for [mirborrowck] borrow-checking [typeofopaque] computing type of opaque [typeof] computing type of [checkwellformed] checking that is well-formed [checkmodtypewf] checking that types are well-formed in top-level module [analysis] running analysis passes on this crate --
cc ^_^
Further reduced:
(Also note that even before this presumably would have still ICEd, because the only thing we did with that error was unwrap it.)
I think the assertion is just incorrect in this case. Normally a BCB will only have a physical counter if it has exactly one in-edge. If it has multiple in-edges, it will get an expression that sums up all of its in-edges, most of which will have physical counters. That's what the assertion is verifying. However, if a BCB has an out-edge back to itself (i.e. ), it gets a physical counter regardless. The assertion doesn't account for this, and fails. Perhaps the reason we haven't seen this before is that the other in-edge to the loop node usually happens to be the one chosen to get an expression, so the assertion never sees the two-counters case.
I tried to fix the assertion itself, but that ended up being pretty complicated. The information we'd need isn't readily available in the place that we need it, and there's also a related assertion in that has the same problem. Given that these assertions aren't load-bearing (the code should work fine even if it's violated), I'm inclined to just rip them out. They only exist as a holdover from the original implementation anyway.

Discussion:
<ins> use std::fmt::{self, Debug}; </ins> use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::graph::WithNumNodes; <del> use rustc_index::bit_set::BitSet; </del> use rustc_index::IndexVec; <del> use rustc_middle::mir::coverage::*; </del> <ins> use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId, Op}; </ins> <del> use super::graph::{BasicCoverageBlock, CoverageGraph, TraverseCoverageGraphWithLoops}; use std::fmt::{self, Debug}; </del> <ins> use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, TraverseCoverageGraphWithLoops}; </ins> /// The coverage counter or counter expression associated with a particular /// BCB node or BCB edge.

} impl BcbCounter { <del> fn is_expression(&self) -> bool { matches!(self, Self::Expression { .. }) } </del> pub(super) fn as_term(&self) -> CovTerm { match *self { BcbCounter::Counter { id, .. } => CovTerm::Counter(id),

/// We currently don't iterate over this map, but if we do in the future, /// switch it back to `FxIndexMap` to avoid query stability hazards. bcb_edge_counters: FxHashMap<(BasicCoverageBlock, BasicCoverageBlock), BcbCounter>, <del> /// Tracks which BCBs have a counter associated with some incoming edge. /// Only used by assertions, to verify that BCBs with incoming edge /// counters do not have their own physical counters (expressions are allowed). bcb_has_incoming_edge_counters: BitSet<BasicCoverageBlock>, </del> /// Table of expression data, associating each expression ID with its /// corresponding operator (+ or -) and its LHS/RHS operands. expressions: IndexVec<ExpressionId, Expression>,

counter_increment_sites: IndexVec::new(), bcb_counters: IndexVec::from_elem_n(None, num_bcbs), bcb_edge_counters: FxHashMap::default(), <del> bcb_has_incoming_edge_counters: BitSet::new_empty(num_bcbs), </del> expressions: IndexVec::new(), };

} fn set_bcb_counter(&mut self, bcb: BasicCoverageBlock, counter_kind: BcbCounter) -> BcbCounter { <del> assert!( // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also // have an expression (to be injected into an existing `BasicBlock` represented by this // `BasicCoverageBlock`). counter_kind.is_expression() || !self.bcb_has_incoming_edge_counters.contains(bcb), "attempt to add a `Counter` to a BCB target with existing incoming edge counters" ); </del> if let Some(replaced) = self.bcb_counters[bcb].replace(counter_kind) { bug!( "attempt to set a BasicCoverageBlock coverage counter more than once; 

to_bcb: BasicCoverageBlock, counter_kind: BcbCounter, ) -> BcbCounter { <del> // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also // have an expression (to be injected into an existing `BasicBlock` represented by this // `BasicCoverageBlock`). if let Some(node_counter) = self.bcb_counter(to_bcb) && !node_counter.is_expression() { bug!( "attempt to add an incoming edge counter from {from_bcb:?}  when the target BCB already has {node_counter:?}" ); } self.bcb_has_incoming_edge_counters.insert(to_bcb); </del> if let Some(replaced) = self.bcb_edge_counters.insert((from_bcb, to_bcb), counter_kind) { bug!( "attempt to set an edge counter more than once; from_bcb: 

<ins> Function name: let_else_loop::_if (unused) Raw bytes (19): 0x[01, 01, 00, 03, 00, 16, 01, 01, 0c, 00, 02, 09, 00, 10, 00, 02, 09, 00, 10] Number of files: 1 - file 0 => global file 1 Number of expressions: 0 Number of file 0 mappings: 3 - Code(Zero) at (prev + 22, 1) to (start + 1, 12) - Code(Zero) at (prev + 2, 9) to (start + 0, 16) - Code(Zero) at (prev + 2, 9) to (start + 0, 16) Function name: let_else_loop::_loop_either_way (unused) Raw bytes (19): 0x[01, 01, 00, 03, 00, 0f, 01, 01, 14, 00, 01, 1c, 00, 23, 00, 01, 05, 00, 0c] Number of files: 1 - file 0 => global file 1 Number of expressions: 0 Number of file 0 mappings: 3 - Code(Zero) at (prev + 15, 1) to (start + 1, 20) - Code(Zero) at (prev + 1, 28) to (start + 0, 35) - Code(Zero) at (prev + 1, 5) to (start + 0, 12) Function name: let_else_loop::loopy Raw bytes (19): 0x[01, 01, 00, 03, 01, 09, 01, 01, 14, 00, 01, 1c, 00, 23, 05, 01, 01, 00, 02] Number of files: 1 - file 0 => global file 1 Number of expressions: 0 Number of file 0 mappings: 3 - Code(Counter(0)) at (prev + 9, 1) to (start + 1, 20) - Code(Zero) at (prev + 1, 28) to (start + 0, 35) - Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2) </ins>

<ins> LL|       |#![feature(coverage_attribute)] LL|       |//@ edition: 2021 LL|       | LL|       |// Regression test for <https://github.com/rust-lang/rust/issues/122738>. LL|       |// These code patterns should not trigger an ICE when allocating a physical LL|       |// counter to a node and also one of its in-edges, because that is allowed LL|       |// when the node contains a tight loop to itself. LL|       | LL|      1|fn loopy(cond: bool) { LL|      1|    let true = cond else { loop {} }; ^0 LL|      1|} LL|       | LL|       |// Variant that also has `loop {}` on the success path. LL|       |// This isn't needed to catch the original ICE, but might help detect regressions. LL|      0|fn _loop_either_way(cond: bool) { LL|      0|    let true = cond else { loop {} }; LL|      0|    loop {} LL|       |} LL|       | LL|       |// Variant using regular `if` instead of let-else. LL|       |// This doesn't trigger the original ICE, but might help detect regressions. LL|      0|fn _if(cond: bool) { LL|      0|    if cond { LL|      0|        loop {} LL|       |    } else { LL|      0|        loop {} LL|       |    } LL|       |} LL|       | LL|       |#[coverage(off)] LL|       |fn main() { LL|       |    loopy(true); LL|       |} </ins>

<ins> #![feature(coverage_attribute)] //@ edition: 2021 // Regression test for <https://github.com/rust-lang/rust/issues/122738>. // These code patterns should not trigger an ICE when allocating a physical // counter to a node and also one of its in-edges, because that is allowed // when the node contains a tight loop to itself. fn loopy(cond: bool) { let true = cond else { loop {} }; } // Variant that also has `loop {}` on the success path. // This isn't needed to catch the original ICE, but might help detect regressions. fn _loop_either_way(cond: bool) { let true = cond else { loop {} }; loop {} } // Variant using regular `if` instead of let-else. // This doesn't trigger the original ICE, but might help detect regressions. fn _if(cond: bool) { if cond { loop {} } else { loop {} } } #[coverage(off)] fn main() { loopy(true); } </ins>