Skip to content

Commit 3a1ab06

Browse files
committed
mir-opt: Eliminate trivial unnecessary storage annotations
1 parent a6dccd7 commit 3a1ab06

File tree

34 files changed

+164
-284
lines changed

34 files changed

+164
-284
lines changed

compiler/rustc_middle/src/mir/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,17 @@ impl<'tcx> BasicBlockData<'tcx> {
14291429
});
14301430
self.after_last_stmt_debuginfos.extend_from_slice(&debuginfos);
14311431
}
1432+
1433+
pub fn strip_nops(&mut self) {
1434+
self.retain_statements(|stmt| !matches!(stmt.kind, StatementKind::Nop))
1435+
}
1436+
1437+
pub fn drop_debuginfo(&mut self) {
1438+
self.after_last_stmt_debuginfos = Vec::new();
1439+
for stmt in self.statements.iter_mut() {
1440+
stmt.debuginfos = Vec::new();
1441+
}
1442+
}
14321443
}
14331444

14341445
///////////////////////////////////////////////////////////////////////////

compiler/rustc_middle/src/mir/statement.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,10 @@ impl<'tcx> Statement<'tcx> {
2727
}
2828
let replaced_stmt = std::mem::replace(&mut self.kind, StatementKind::Nop);
2929
if !drop_debuginfo {
30-
match replaced_stmt {
31-
StatementKind::Assign(box (place, Rvalue::Ref(_, _, ref_place)))
32-
if let Some(local) = place.as_local() =>
33-
{
34-
self.debuginfos.push(StmtDebugInfo::AssignRef(local, ref_place));
35-
}
36-
_ => {
37-
bug!("debuginfo is not yet supported.")
38-
}
39-
}
30+
let Some(debuginfo) = replaced_stmt.as_debuginfo() else {
31+
bug!("debuginfo is not yet supported.")
32+
};
33+
self.debuginfos.push(debuginfo);
4034
}
4135
}
4236

@@ -79,6 +73,17 @@ impl<'tcx> StatementKind<'tcx> {
7973
_ => None,
8074
}
8175
}
76+
77+
pub fn as_debuginfo(&self) -> Option<StmtDebugInfo<'tcx>> {
78+
match self {
79+
StatementKind::Assign(box (place, Rvalue::Ref(_, _, ref_place)))
80+
if let Some(local) = place.as_local() =>
81+
{
82+
Some(StmtDebugInfo::AssignRef(local, *ref_place))
83+
}
84+
_ => None,
85+
}
86+
}
8287
}
8388

8489
///////////////////////////////////////////////////////////////////////////

compiler/rustc_mir_dataflow/src/impls/liveness.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,10 @@ impl<'a> MaybeTransitiveLiveLocals<'a> {
228228
// Compute the place that we are storing to, if any
229229
let destination = match stmt_kind {
230230
StatementKind::Assign(box (place, rvalue)) => (rvalue.is_safe_to_remove()
231+
// FIXME: We are not sure how we should represent this debugging information for some statements,
232+
// keep it for now.
231233
&& (!debuginfo_locals.contains(place.local)
232-
|| (place.as_local().is_some() && matches!(rvalue, mir::Rvalue::Ref(..)))))
234+
|| (place.as_local().is_some() && stmt_kind.as_debuginfo().is_some())))
233235
.then_some(*place),
234236
StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
235237
(!debuginfo_locals.contains(place.local)).then_some(**place)

compiler/rustc_mir_transform/src/dead_store_elimination.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ use rustc_mir_dataflow::impls::{
2222
LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals,
2323
};
2424

25+
use crate::simplify::UsedInStmtLocals;
2526
use crate::util::is_within_packed;
2627

2728
/// Performs the optimization on the body
2829
///
2930
/// The `borrowed` set must be a `DenseBitSet` of all the locals that are ever borrowed in this
3031
/// body. It can be generated via the [`borrowed_locals`] function.
31-
fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
32+
fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
3233
let borrowed_locals = borrowed_locals(body);
3334

3435
// If the user requests complete debuginfo, mark the locals that appear in it as live, so
@@ -89,8 +90,9 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
8990
}
9091

9192
if patch.is_empty() && call_operands_to_move.is_empty() {
92-
return;
93+
return false;
9394
}
95+
let eliminated = !patch.is_empty();
9496

9597
let bbs = body.basic_blocks.as_mut_preserves_cfg();
9698
for (Location { block, statement_index }, drop_debuginfo) in patch {
@@ -104,6 +106,8 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
104106
let Operand::Copy(place) = *arg else { bug!() };
105107
*arg = Operand::Move(place);
106108
}
109+
110+
eliminated
107111
}
108112

109113
pub(super) enum DeadStoreElimination {
@@ -124,7 +128,12 @@ impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination {
124128
}
125129

126130
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
127-
eliminate(tcx, body);
131+
if eliminate(tcx, body) {
132+
UsedInStmtLocals::new(body).remove_unused_storage_annotations(body);
133+
for data in body.basic_blocks.as_mut_preserves_cfg() {
134+
data.strip_nops();
135+
}
136+
}
128137
}
129138

130139
fn is_required(&self) -> bool {

compiler/rustc_mir_transform/src/inline.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tracing::{debug, instrument, trace, trace_span};
2121

2222
use crate::cost_checker::{CostChecker, is_call_like};
2323
use crate::deref_separator::deref_finder;
24-
use crate::simplify::simplify_cfg;
24+
use crate::simplify::{UsedInStmtLocals, simplify_cfg};
2525
use crate::validate::validate_types;
2626
use crate::{check_inline, util};
2727

@@ -935,7 +935,7 @@ fn inline_call<'tcx, I: Inliner<'tcx>>(
935935
in_cleanup_block: false,
936936
return_block,
937937
tcx,
938-
always_live_locals: DenseBitSet::new_filled(callee_body.local_decls.len()),
938+
always_live_locals: UsedInStmtLocals::new(&callee_body).locals,
939939
};
940940

941941
// Map all `Local`s, `SourceScope`s and `BasicBlock`s to new ones
@@ -995,6 +995,10 @@ fn inline_call<'tcx, I: Inliner<'tcx>>(
995995
// people working on rust can build with or without debuginfo while
996996
// still getting consistent results from the mir-opt tests.
997997
caller_body.var_debug_info.append(&mut callee_body.var_debug_info);
998+
} else {
999+
for bb in callee_body.basic_blocks_mut() {
1000+
bb.drop_debuginfo();
1001+
}
9981002
}
9991003
caller_body.basic_blocks_mut().append(callee_body.basic_blocks_mut());
10001004

compiler/rustc_mir_transform/src/simplify.rs

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@
3434
//! The normal logic that a program with UB can be changed to do anything does not apply to
3535
//! pre-"runtime" MIR!
3636
37+
use rustc_index::bit_set::DenseBitSet;
3738
use rustc_index::{Idx, IndexSlice, IndexVec};
38-
use rustc_middle::mir::visit::{
39-
MutVisitor, MutatingUseContext, NonUseContext, PlaceContext, Visitor,
40-
};
39+
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
4140
use rustc_middle::mir::*;
4241
use rustc_middle::ty::TyCtxt;
42+
use rustc_mir_dataflow::debuginfo::debuginfo_locals;
4343
use rustc_span::DUMMY_SP;
4444
use smallvec::SmallVec;
4545
use tracing::{debug, trace};
@@ -344,7 +344,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
344344

345345
fn strip_nops(&mut self) {
346346
for blk in self.basic_blocks.iter_mut() {
347-
blk.retain_statements(|stmt| !matches!(stmt.kind, StatementKind::Nop))
347+
blk.strip_nops();
348348
}
349349
}
350350
}
@@ -517,28 +517,39 @@ fn make_local_map<V>(
517517
/// Keeps track of used & unused locals.
518518
struct UsedLocals {
519519
increment: bool,
520-
arg_count: u32,
521520
use_count: IndexVec<Local, u32>,
521+
always_used: DenseBitSet<Local>,
522522
}
523523

524524
impl UsedLocals {
525525
/// Determines which locals are used & unused in the given body.
526526
fn new(body: &Body<'_>) -> Self {
527+
let mut always_used = debuginfo_locals(body);
528+
always_used.insert(RETURN_PLACE);
529+
for arg in body.args_iter() {
530+
always_used.insert(arg);
531+
}
527532
let mut this = Self {
528533
increment: true,
529-
arg_count: body.arg_count.try_into().unwrap(),
530534
use_count: IndexVec::from_elem(0, &body.local_decls),
535+
always_used,
531536
};
532537
this.visit_body(body);
533538
this
534539
}
535540

536541
/// Checks if local is used.
537542
///
538-
/// Return place and arguments are always considered used.
543+
/// Return place, arguments, var debuginfo are always considered used.
539544
fn is_used(&self, local: Local) -> bool {
540-
trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]);
541-
local.as_u32() <= self.arg_count || self.use_count[local] != 0
545+
trace!(
546+
"is_used({:?}): use_count: {:?}, always_used: {}",
547+
local,
548+
self.use_count[local],
549+
self.always_used.contains(local)
550+
);
551+
// To keep things simple, we don't handle debugging information here, these are in DSE.
552+
self.always_used.contains(local) || self.use_count[local] != 0
542553
}
543554

544555
/// Updates the use counts to reflect the removal of given statement.
@@ -583,13 +594,10 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
583594
StatementKind::ConstEvalCounter
584595
| StatementKind::Nop
585596
| StatementKind::StorageLive(..)
586-
| StatementKind::StorageDead(..) => {
587-
self.visit_statement_debuginfos(&statement.debuginfos, location);
588-
}
597+
| StatementKind::StorageDead(..) => {}
589598

590599
StatementKind::Assign(box (ref place, ref rvalue)) => {
591600
if rvalue.is_safe_to_remove() {
592-
self.visit_statement_debuginfos(&statement.debuginfos, location);
593601
self.visit_lhs(place, location);
594602
self.visit_rvalue(rvalue, location);
595603
} else {
@@ -600,16 +608,18 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
600608
StatementKind::SetDiscriminant { ref place, variant_index: _ }
601609
| StatementKind::Deinit(ref place)
602610
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => {
603-
self.visit_statement_debuginfos(&statement.debuginfos, location);
604611
self.visit_lhs(place, location);
605612
}
606613
}
607614
}
608615

609616
fn visit_local(&mut self, local: Local, ctx: PlaceContext, _location: Location) {
617+
if matches!(ctx, PlaceContext::NonUse(_)) {
618+
return;
619+
}
610620
if self.increment {
611621
self.use_count[local] += 1;
612-
} else if ctx != PlaceContext::NonUse(NonUseContext::VarDebugInfo) {
622+
} else {
613623
assert_ne!(self.use_count[local], 0);
614624
self.use_count[local] -= 1;
615625
}
@@ -629,28 +639,26 @@ fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Bod
629639

630640
for data in body.basic_blocks.as_mut_preserves_cfg() {
631641
// Remove unnecessary StorageLive and StorageDead annotations.
632-
data.retain_statements(|statement| {
633-
let keep = match &statement.kind {
642+
for statement in data.statements.iter_mut() {
643+
let keep_statement = match &statement.kind {
634644
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
635645
used_locals.is_used(*local)
636646
}
637-
StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
638-
639-
StatementKind::SetDiscriminant { place, .. }
640-
| StatementKind::BackwardIncompatibleDropHint { place, reason: _ }
641-
| StatementKind::Deinit(place) => used_locals.is_used(place.local),
642-
StatementKind::Nop => false,
643-
_ => true,
647+
StatementKind::Assign(box (place, _))
648+
| StatementKind::SetDiscriminant { box place, .. }
649+
| StatementKind::BackwardIncompatibleDropHint { box place, .. }
650+
| StatementKind::Deinit(box place) => used_locals.is_used(place.local),
651+
_ => continue,
644652
};
645-
646-
if !keep {
647-
trace!("removing statement {:?}", statement);
648-
modified = true;
649-
used_locals.statement_removed(statement);
653+
if keep_statement {
654+
continue;
650655
}
651-
652-
keep
653-
});
656+
trace!("removing statement {:?}", statement);
657+
modified = true;
658+
used_locals.statement_removed(statement);
659+
statement.make_nop(true);
660+
}
661+
data.strip_nops();
654662
}
655663
}
656664
}
@@ -669,3 +677,42 @@ impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> {
669677
*l = self.map[*l].unwrap();
670678
}
671679
}
680+
681+
pub(crate) struct UsedInStmtLocals {
682+
pub(crate) locals: DenseBitSet<Local>,
683+
}
684+
685+
impl UsedInStmtLocals {
686+
pub(crate) fn new(body: &Body<'_>) -> Self {
687+
let mut this = Self { locals: DenseBitSet::new_empty(body.local_decls.len()) };
688+
this.visit_body(body);
689+
this
690+
}
691+
692+
pub(crate) fn remove_unused_storage_annotations<'tcx>(&self, body: &mut Body<'tcx>) {
693+
for data in body.basic_blocks.as_mut_preserves_cfg() {
694+
// Remove unnecessary StorageLive and StorageDead annotations.
695+
for statement in data.statements.iter_mut() {
696+
let keep_statement = match &statement.kind {
697+
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
698+
self.locals.contains(*local)
699+
}
700+
_ => continue,
701+
};
702+
if keep_statement {
703+
continue;
704+
}
705+
statement.make_nop(true);
706+
}
707+
}
708+
}
709+
}
710+
711+
impl<'tcx> Visitor<'tcx> for UsedInStmtLocals {
712+
fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
713+
if matches!(context, PlaceContext::NonUse(_)) {
714+
return;
715+
}
716+
self.locals.insert(local);
717+
}
718+
}

tests/mir-opt/dead-store-elimination/cycle.cycle.DeadStoreElimination-initial.diff

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
- _3 = copy _2;
2020
- _2 = copy _1;
2121
- _1 = copy _5;
22-
+ nop;
23-
+ nop;
24-
+ nop;
25-
+ nop;
2622
_4 = cond() -> [return: bb1, unwind continue];
2723
}
2824

tests/mir-opt/dead-store-elimination/ref.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub fn tuple(v: (i32, &Foo)) -> i32 {
1212
// CHECK: debug _dead => [[dead:_[0-9]+]];
1313
// CHECK: bb0:
1414
// FIXME: Preserve `tmp` for debuginfo, but we can merge it into the debuginfo.
15-
// CHECK: [[tmp:_[0-9]+]] = deref_copy (_1.1: &Foo);
15+
// CHECK-NEXT: [[tmp:_[0-9]+]] = deref_copy (_1.1: &Foo);
1616
// CHECK-NEXT: DBG: [[dead]] = &((*[[tmp]]).2: i32)
1717
let _dead = &v.1.c;
1818
v.1.a

tests/mir-opt/dead-store-elimination/ref.tuple.DeadStoreElimination-initial.diff

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
}
1313

1414
bb0: {
15-
StorageLive(_2);
15+
- StorageLive(_2);
1616
_3 = deref_copy (_1.1: &Foo);
1717
- _2 = &((*_3).2: i32);
1818
+ // DBG: _2 = &((*_3).2: i32)
19-
+ nop;
2019
_4 = deref_copy (_1.1: &Foo);
2120
_0 = copy ((*_4).0: i32);
22-
StorageDead(_2);
21+
- StorageDead(_2);
2322
return;
2423
}
2524
}

tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@
1414
-
1515
- bb1: {
1616
- // DBG: _3 = &((*_1).0: i32)
17-
- nop;
1817
- goto -> bb2;
1918
- }
2019
-
2120
- bb2: {
2221
// DBG: _4 = &((*_1).1: i64)
23-
- nop;
2422
_0 = copy ((*_1).2: i32);
2523
return;
2624
}

0 commit comments

Comments
 (0)