Change:
tracking issue: Inspired by comment in For things like it'd be nice to not have to even give LLVM the code for the else branch to have to emit. Ought to save time and binary size. Tentatively marked mir-opt, since we presumably still need to make the MIR for it, in order to borrow-check and such the unreachable code. But might need to be codegen-time instead to pick up on monomorphized inline constants. Hopefully should be doable relatively easily when the const is an inline const, since that forces const folding already, and thus there should just be a in the MIR.
Well, turns out this already works for simple constants in MIR! However, the place I actually needed it was more like , which doesn't work with today. So maybe come back to this once that's fixed.
This should be done by which AFAIK is run unconditionally. I wonder if its match needs to be expanded? Is there mcve?
The problem is that this can't be done on generic constants, as we don't mir-optimize post-monomorphization. We could start doing some benchmarks to see how bad running all mir opts (or at least a select few) would be if we duplicated, substituted and optimized MIR bodies for concrete generic parameters.
I would say qualifies as an MCVE as the monomorphized gives the following llvm ir
Maybe it should also be evaluated together with polymorphization or constevaluatablechecked since those can cut down on the duplication before applying further optimizations.
Bonus points if this can avoid monomorphizing functions called in the dead code too. Demo extracted from : (cc ) Would be great to not codegen all the downstream stuff that's only called in the else block of a .
FYI this should work with
Totally wild (and probably bad) idea: could we avoid borrow-checking unreachable code? After all, the purpose of borrow-checking is to prevent memory unsafety at runtime, but unreachable code cannot be memory unsafe, because it never executes!
We already do! As a , (But this isn't really the right issue to discuss possible extensions to that.)
This seems closely related to and
recent PR seems related here? If the condition becomes a constant during codegen, we should be entirely skipping it now and not even generate LLVM IR. However, if that dead code calls functions, those still get codegen'd as usual -- the collector that determines which function to codegen doesn't know about dead branches. We'd have to embed a (simple) monomorphization-time reachability analysis in the collector to fix that.

Discussion:
.generic_activity("codegen prelude") .run(|| crate::abi::codegen_fn_prelude(fx, start_block)); <del> for (bb, bb_data) in fx.mir.basic_blocks.iter_enumerated() { </del> <ins> for (bb, bb_data) in traversal::mono_reachable(fx.mir, fx.tcx, fx.instance) { </ins> let block = fx.get_block(bb); fx.bcx.switch_to_block(block);

// Apply debuginfo to the newly allocated locals. fx.debug_introduce_locals(&mut start_bx); <del> let reachable_blocks = mir.reachable_blocks_in_mono(cx.tcx(), instance); </del> // The builders will be created separately for each basic block at `codegen_block`. // So drop the builder of `start_llbb` to avoid having two at the same time. drop(start_bx); <ins> let reachable_blocks = traversal::mono_reachable_as_bitset(mir, cx.tcx(), instance); </ins> // Codegen the body of each block using reverse postorder for (bb, _) in traversal::reverse_postorder(mir) { if reachable_blocks.contains(bb) { fx.codegen_block(bb); } else { <del> // This may have references to things we didn't monomorphize, so we // don't actually codegen the body. We still create the block so // terminators in other blocks can reference it without worry. </del> <ins> // We want to skip this block, because it's not reachable. But we still create // the block so terminators in other blocks can reference it. </ins> fx.codegen_block_as_unreachable(bb); } }

use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::graph::dominators::Dominators; <del> use rustc_data_structures::stack::ensure_sufficient_stack; </del> use rustc_index::bit_set::BitSet; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_serialize::{Decodable, Encodable};

self.injection_phase.is_some() } <del> /// Finds which basic blocks are actually reachable for a specific /// monomorphization of this body. /// /// This is allowed to have false positives; just because this says a block /// is reachable doesn't mean that's necessarily true. It's thus always /// legal for this to return a filled set. /// /// Regardless, the [`BitSet::domain_size`] of the returned set will always /// exactly match the number of blocks in the body so that `contains` /// checks can be done without worrying about panicking. /// /// This is mostly useful because it lets us skip lowering the `false` side /// of `if <T as Trait>::CONST`, as well as `intrinsics::debug_assertions`. pub fn reachable_blocks_in_mono( &self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, ) -> BitSet<BasicBlock> { let mut set = BitSet::new_empty(self.basic_blocks.len()); self.reachable_blocks_in_mono_from(tcx, instance, &mut set, START_BLOCK); set } fn reachable_blocks_in_mono_from( &self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, set: &mut BitSet<BasicBlock>, bb: BasicBlock, ) { if !set.insert(bb) { return; } let data = &self.basic_blocks[bb]; if let Some((bits, targets)) = Self::try_const_mono_switchint(tcx, instance, data) { let target = targets.target_for_value(bits); ensure_sufficient_stack(|| { self.reachable_blocks_in_mono_from(tcx, instance, set, target) }); return; } for target in data.terminator().successors() { ensure_sufficient_stack(|| { self.reachable_blocks_in_mono_from(tcx, instance, set, target) }); } } </del> /// If this basic block ends with a [`TerminatorKind::SwitchInt`] for which we can evaluate the /// dimscriminant in monomorphization, we return the discriminant bits and the /// [`SwitchTargets`], just so the caller doesn't also have to match on the terminator.

/// Returns a `BitSet` containing all basic blocks reachable from the `START_BLOCK`. pub fn reachable_as_bitset(body: &Body<'_>) -> BitSet<BasicBlock> { let mut iter = preorder(body); <del> iter.by_ref().for_each(drop); </del> <ins> while let Some(_) = iter.next() {} </ins> iter.visited }

{ body.basic_blocks.reverse_postorder().iter().map(|&bb| (bb, &body.basic_blocks[bb])) } <ins> /// Traversal of a [`Body`] that tries to avoid unreachable blocks in a monomorphized [`Instance`]. /// /// This is allowed to have false positives; blocks may be visited even if they are not actually /// reachable. /// /// Such a traversal is mostly useful because it lets us skip lowering the `false` side /// of `if <T as Trait>::CONST`, as well as [`NullOp::UbChecks`]. /// /// [`NullOp::UbChecks`]: rustc_middle::mir::NullOp::UbChecks pub fn mono_reachable<'a, 'tcx>( body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, ) -> MonoReachable<'a, 'tcx> { MonoReachable::new(body, tcx, instance) } /// [`MonoReachable`] internally accumulates a [`BitSet`] of visited blocks. This is just a /// convenience function to run that traversal then extract its set of reached blocks. pub fn mono_reachable_as_bitset<'a, 'tcx>( body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, ) -> BitSet<BasicBlock> { let mut iter = mono_reachable(body, tcx, instance); while let Some(_) = iter.next() {} iter.visited } pub struct MonoReachable<'a, 'tcx> { body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, visited: BitSet<BasicBlock>, // Other traversers track their worklist in a Vec. But we don't care about order, so we can // store ours in a BitSet and thus save allocations because BitSet has a small size // optimization. worklist: BitSet<BasicBlock>, } impl<'a, 'tcx> MonoReachable<'a, 'tcx> { pub fn new( body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, ) -> MonoReachable<'a, 'tcx> { let mut worklist = BitSet::new_empty(body.basic_blocks.len()); worklist.insert(START_BLOCK); MonoReachable { body, tcx, instance, visited: BitSet::new_empty(body.basic_blocks.len()), worklist, } } fn add_work(&mut self, blocks: impl IntoIterator<Item = BasicBlock>) { for block in blocks.into_iter() { if !self.visited.contains(block) { self.worklist.insert(block); } } } } impl<'a, 'tcx> Iterator for MonoReachable<'a, 'tcx> { type Item = (BasicBlock, &'a BasicBlockData<'tcx>); fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> { while let Some(idx) = self.worklist.iter().next() { self.worklist.remove(idx); if !self.visited.insert(idx) { continue; } let data = &self.body[idx]; if let Some((bits, targets)) = Body::try_const_mono_switchint(self.tcx, self.instance, data) { let target = targets.target_for_value(bits); self.add_work([target]); } else { self.add_work(data.terminator().successors()); } return Some((idx, data)); } None } } </ins>

use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::interpret::{AllocId, ErrorHandled, GlobalAlloc, Scalar}; use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; <ins> use rustc_middle::mir::traversal; </ins> use rustc_middle::mir::visit::Visitor as MirVisitor; use rustc_middle::mir::{self, Location, MentionedItem}; use rustc_middle::query::TyCtxtAt;

}; if mode == CollectionMode::UsedItems { <del> // Visit everything. Here we rely on the visitor also visiting `required_consts`, so that we // evaluate them and abort compilation if any of them errors. collector.visit_body(body); } else { // We only need to evaluate all constants, but can ignore the rest of the MIR. for const_op in &body.required_consts { if let Some(val) = collector.eval_constant(const_op) { collect_const_value(tcx, val, mentioned_items); } </del> <ins> for (bb, data) in traversal::mono_reachable(body, tcx, instance) { collector.visit_basic_block_data(bb, data) } } // Always visit all `required_consts`, so that we evaluate them and abort compilation if any of // them errors. for const_op in &body.required_consts { if let Some(val) = collector.eval_constant(const_op) { collect_const_value(tcx, val, mentioned_items); </ins> } }