| //! This module implements a dead store elimination (DSE) routine. | |
| //! | |
| //! This transformation was written specifically for the needs of dest prop. Although it is | |
| //! perfectly sound to use it in any context that might need it, its behavior should not be changed | |
| //! without analyzing the interaction this will have with dest prop. Specifically, in addition to | |
| //! the soundness of this pass in general, dest prop needs it to satisfy two additional conditions: | |
| //! | |
| //! 1. It's idempotent, meaning that running this pass a second time immediately after running it a | |
| //! first time will not cause any further changes. | |
| //! 2. This idempotence persists across dest prop's main transform, in other words inserting any | |
| //! number of iterations of dest prop between the first and second application of this transform | |
| //! will still not cause any further changes. | |
| //! | |
| use rustc_middle::bug; | |
| use rustc_middle::mir::visit::Visitor; | |
| use rustc_middle::mir::*; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_mir_dataflow::Analysis; | |
| use rustc_mir_dataflow::debuginfo::debuginfo_locals; | |
| use rustc_mir_dataflow::impls::{ | |
| LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals, | |
| }; | |
| use crate::simplify::UsedInStmtLocals; | |
| use crate::util::most_packed_projection; | |
| /// Performs the optimization on the body | |
| /// | |
| /// The `borrowed` set must be a `DenseBitSet` of all the locals that are ever borrowed in this | |
| /// body. It can be generated via the [`borrowed_locals`] function. | |
| /// Returns true if any instruction is eliminated. | |
| fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { | |
| let borrowed_locals = borrowed_locals(body); | |
| // If the user requests complete debuginfo, mark the locals that appear in it as live, so | |
| // we don't remove assignments to them. | |
| let debuginfo_locals = debuginfo_locals(body); | |
| let mut live = MaybeTransitiveLiveLocals::new(&borrowed_locals, &debuginfo_locals) | |
| .iterate_to_fixpoint(tcx, body, None) | |
| .into_results_cursor(body); | |
| // For blocks with a call terminator, if an argument copy can be turned into a move, | |
| // record it as (block, argument index). | |
| let mut call_operands_to_move = Vec::new(); | |
| let mut patch = Vec::new(); | |
| for (bb, bb_data) in traversal::preorder(body) { | |
| if let TerminatorKind::Call { ref args, ref destination, .. } = bb_data.terminator().kind { | |
| let loc = Location { block: bb, statement_index: bb_data.statements.len() }; | |
| // Position ourselves between the evaluation of `args` and the write to `destination`. | |
| live.seek_to_block_end(bb); | |
| let mut state = live.get().clone(); | |
| // Don't turn into a move if the local is used as an index | |
| // projection for the destination place. | |
| LivenessTransferFunction(&mut state).visit_place( | |
| destination, | |
| visit::PlaceContext::MutatingUse(visit::MutatingUseContext::Call), | |
| loc, | |
| ); | |
| for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() { | |
| if let Operand::Copy(place) = *arg | |
| && !place.is_indirect() | |
| // Do not skip the transformation if the local is in debuginfo, as we do | |
| // not really lose any information for this purpose. | |
| && !borrowed_locals.contains(place.local) | |
| && !state.contains(place.local) | |
| // If `place` is a projection of a disaligned field in a packed ADT, | |
| // the move may be codegened as a pointer to that field. | |
| // Using that disaligned pointer may trigger UB in the callee, | |
| // so do nothing. | |
| && most_packed_projection(tcx, body, place).is_none() | |
| { | |
| call_operands_to_move.push((bb, index)); | |
| } | |
| // Account that `arg` is read from, so we don't promote another argument to a move. | |
| LivenessTransferFunction(&mut state).visit_operand(arg, loc); | |
| } | |
| } | |
| for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { | |
| if let Some(destination) = MaybeTransitiveLiveLocals::can_be_removed_if_dead( | |
| &statement.kind, | |
| &borrowed_locals, | |
| &debuginfo_locals, | |
| ) { | |
| let loc = Location { block: bb, statement_index }; | |
| live.seek_before_primary_effect(loc); | |
| if !live.get().contains(destination.local) { | |
| let drop_debuginfo = !debuginfo_locals.contains(destination.local); | |
| // When eliminating a dead statement, we need to address | |
| // the debug information for that statement. | |
| assert!( | |
| drop_debuginfo || statement.kind.as_debuginfo().is_some(), | |
| "don't know how to retain the debug information for {:?}", | |
| statement.kind | |
| ); | |
| patch.push((loc, drop_debuginfo)); | |
| } | |
| } | |
| } | |
| } | |
| if patch.is_empty() && call_operands_to_move.is_empty() { | |
| return false; | |
| } | |
| let eliminated = !patch.is_empty(); | |
| let bbs = body.basic_blocks.as_mut_preserves_cfg(); | |
| for (Location { block, statement_index }, drop_debuginfo) in patch { | |
| bbs[block].statements[statement_index].make_nop(drop_debuginfo); | |
| } | |
| for (block, argument_index) in call_operands_to_move { | |
| let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else { | |
| bug!() | |
| }; | |
| let arg = &mut args[argument_index].node; | |
| let Operand::Copy(place) = *arg else { bug!() }; | |
| *arg = Operand::Move(place); | |
| } | |
| eliminated | |
| } | |
| pub(super) enum DeadStoreElimination { | |
| Initial, | |
| Final, | |
| } | |
| impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination { | |
| fn name(&self) -> &'static str { | |
| match self { | |
| DeadStoreElimination::Initial => "DeadStoreElimination-initial", | |
| DeadStoreElimination::Final => "DeadStoreElimination-final", | |
| } | |
| } | |
| fn is_enabled(&self, sess: &rustc_session::Session) -> bool { | |
| sess.mir_opt_level() >= 2 | |
| } | |
| fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
| if eliminate(tcx, body) { | |
| UsedInStmtLocals::new(body).remove_unused_storage_annotations(body); | |
| for data in body.basic_blocks.as_mut_preserves_cfg() { | |
| data.strip_nops(); | |
| } | |
| } | |
| } | |
| fn is_required(&self) -> bool { | |
| false | |
| } | |
| } |