Skip to content

Commit a4d30a7

Browse files
committed
Auto merge of #77876 - tmiasko:simplify-locals, r=wesleywiser
Remove unused set-discriminant statements and assignments regardless of rvalue * Represent use counts with u32 * Unify use count visitors * Change RemoveStatements visitor into a function * Remove unused set-discriminant statements * Use exhaustive match to clarify what is being optimized * Remove unused assignments regardless of rvalue kind
2 parents fd54259 + 4c3e06a commit a4d30a7

22 files changed

+404
-163
lines changed

compiler/rustc_mir/src/transform/simplify.rs

+110-143
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use rustc_middle::mir::*;
3535
use rustc_middle::ty::TyCtxt;
3636
use smallvec::SmallVec;
3737
use std::borrow::Cow;
38+
use std::convert::TryInto;
3839

3940
pub struct SimplifyCfg {
4041
label: String,
@@ -322,32 +323,17 @@ impl<'tcx> MirPass<'tcx> for SimplifyLocals {
322323
trace!("running SimplifyLocals on {:?}", body.source);
323324

324325
// First, we're going to get a count of *actual* uses for every `Local`.
325-
// Take a look at `DeclMarker::visit_local()` to see exactly what is ignored.
326-
let mut used_locals = {
327-
let mut marker = DeclMarker::new(body);
328-
marker.visit_body(&body);
329-
330-
marker.local_counts
331-
};
332-
333-
let arg_count = body.arg_count;
326+
let mut used_locals = UsedLocals::new(body);
334327

335328
// Next, we're going to remove any `Local` with zero actual uses. When we remove those
336329
// `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
337330
// count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
338331
// `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
339332
// fixedpoint where there are no more unused locals.
340-
loop {
341-
let mut remove_statements = RemoveStatements::new(&mut used_locals, arg_count, tcx);
342-
remove_statements.visit_body(body);
343-
344-
if !remove_statements.modified {
345-
break;
346-
}
347-
}
333+
remove_unused_definitions(&mut used_locals, body);
348334

349335
// Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the `Local`s.
350-
let map = make_local_map(&mut body.local_decls, used_locals, arg_count);
336+
let map = make_local_map(&mut body.local_decls, &used_locals);
351337

352338
// Only bother running the `LocalUpdater` if we actually found locals to remove.
353339
if map.iter().any(Option::is_none) {
@@ -363,14 +349,14 @@ impl<'tcx> MirPass<'tcx> for SimplifyLocals {
363349
/// Construct the mapping while swapping out unused stuff out from the `vec`.
364350
fn make_local_map<V>(
365351
local_decls: &mut IndexVec<Local, V>,
366-
used_locals: IndexVec<Local, usize>,
367-
arg_count: usize,
352+
used_locals: &UsedLocals,
368353
) -> IndexVec<Local, Option<Local>> {
369354
let mut map: IndexVec<Local, Option<Local>> = IndexVec::from_elem(None, &*local_decls);
370355
let mut used = Local::new(0);
371-
for (alive_index, count) in used_locals.iter_enumerated() {
372-
// The `RETURN_PLACE` and arguments are always live.
373-
if alive_index.as_usize() > arg_count && *count == 0 {
356+
357+
for alive_index in local_decls.indices() {
358+
// `is_used` treats the `RETURN_PLACE` and arguments as used.
359+
if !used_locals.is_used(alive_index) {
374360
continue;
375361
}
376362

@@ -384,149 +370,130 @@ fn make_local_map<V>(
384370
map
385371
}
386372

387-
struct DeclMarker<'a, 'tcx> {
388-
pub local_counts: IndexVec<Local, usize>,
389-
pub body: &'a Body<'tcx>,
373+
/// Keeps track of used & unused locals.
374+
struct UsedLocals {
375+
increment: bool,
376+
arg_count: u32,
377+
use_count: IndexVec<Local, u32>,
390378
}
391379

392-
impl<'a, 'tcx> DeclMarker<'a, 'tcx> {
393-
pub fn new(body: &'a Body<'tcx>) -> Self {
394-
Self { local_counts: IndexVec::from_elem(0, &body.local_decls), body }
380+
impl UsedLocals {
381+
/// Determines which locals are used & unused in the given body.
382+
fn new(body: &Body<'_>) -> Self {
383+
let mut this = Self {
384+
increment: true,
385+
arg_count: body.arg_count.try_into().unwrap(),
386+
use_count: IndexVec::from_elem(0, &body.local_decls),
387+
};
388+
this.visit_body(body);
389+
this
395390
}
396-
}
397391

398-
impl<'a, 'tcx> Visitor<'tcx> for DeclMarker<'a, 'tcx> {
399-
fn visit_local(&mut self, local: &Local, ctx: PlaceContext, location: Location) {
400-
// Ignore storage markers altogether, they get removed along with their otherwise unused
401-
// decls.
402-
// FIXME: Extend this to all non-uses.
403-
if ctx.is_storage_marker() {
404-
return;
405-
}
392+
/// Checks if local is used.
393+
///
394+
/// Return place and arguments are always considered used.
395+
fn is_used(&self, local: Local) -> bool {
396+
trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]);
397+
local.as_u32() <= self.arg_count || self.use_count[local] != 0
398+
}
406399

407-
// Ignore stores of constants because `ConstProp` and `CopyProp` can remove uses of many
408-
// of these locals. However, if the local is still needed, then it will be referenced in
409-
// another place and we'll mark it as being used there.
410-
if ctx == PlaceContext::MutatingUse(MutatingUseContext::Store)
411-
|| ctx == PlaceContext::MutatingUse(MutatingUseContext::Projection)
412-
{
413-
let block = &self.body.basic_blocks()[location.block];
414-
if location.statement_index != block.statements.len() {
415-
let stmt = &block.statements[location.statement_index];
416-
417-
if let StatementKind::Assign(box (dest, rvalue)) = &stmt.kind {
418-
if !dest.is_indirect() && dest.local == *local {
419-
let can_skip = match rvalue {
420-
Rvalue::Use(_)
421-
| Rvalue::Discriminant(_)
422-
| Rvalue::BinaryOp(_, _, _)
423-
| Rvalue::CheckedBinaryOp(_, _, _)
424-
| Rvalue::Repeat(_, _)
425-
| Rvalue::AddressOf(_, _)
426-
| Rvalue::Len(_)
427-
| Rvalue::UnaryOp(_, _)
428-
| Rvalue::Aggregate(_, _) => true,
429-
430-
_ => false,
431-
};
432-
433-
if can_skip {
434-
trace!("skipping store of {:?} to {:?}", rvalue, dest);
435-
return;
436-
}
437-
}
438-
}
439-
}
440-
}
400+
/// Updates the use counts to reflect the removal of given statement.
401+
fn statement_removed(&mut self, statement: &Statement<'tcx>) {
402+
self.increment = false;
441403

442-
self.local_counts[*local] += 1;
404+
// The location of the statement is irrelevant.
405+
let location = Location { block: START_BLOCK, statement_index: 0 };
406+
self.visit_statement(statement, location);
443407
}
444-
}
445-
446-
struct StatementDeclMarker<'a, 'tcx> {
447-
used_locals: &'a mut IndexVec<Local, usize>,
448-
statement: &'a Statement<'tcx>,
449-
}
450408

451-
impl<'a, 'tcx> StatementDeclMarker<'a, 'tcx> {
452-
pub fn new(
453-
used_locals: &'a mut IndexVec<Local, usize>,
454-
statement: &'a Statement<'tcx>,
455-
) -> Self {
456-
Self { used_locals, statement }
409+
/// Visits a left-hand side of an assignment.
410+
fn visit_lhs(&mut self, place: &Place<'tcx>, location: Location) {
411+
if place.is_indirect() {
412+
// A use, not a definition.
413+
self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
414+
} else {
415+
// A definition. Although, it still might use other locals for indexing.
416+
self.super_projection(
417+
place.local,
418+
&place.projection,
419+
PlaceContext::MutatingUse(MutatingUseContext::Projection),
420+
location,
421+
);
422+
}
457423
}
458424
}
459425

460-
impl<'a, 'tcx> Visitor<'tcx> for StatementDeclMarker<'a, 'tcx> {
461-
fn visit_local(&mut self, local: &Local, context: PlaceContext, _location: Location) {
462-
// Skip the lvalue for assignments
463-
if let StatementKind::Assign(box (p, _)) = self.statement.kind {
464-
if p.local == *local && context.is_place_assignment() {
465-
return;
426+
impl Visitor<'_> for UsedLocals {
427+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
428+
match statement.kind {
429+
StatementKind::LlvmInlineAsm(..)
430+
| StatementKind::Retag(..)
431+
| StatementKind::Coverage(..)
432+
| StatementKind::FakeRead(..)
433+
| StatementKind::AscribeUserType(..) => {
434+
self.super_statement(statement, location);
466435
}
467-
}
468436

469-
let use_count = &mut self.used_locals[*local];
470-
// If this is the local we're removing...
471-
if *use_count != 0 {
472-
*use_count -= 1;
473-
}
474-
}
475-
}
437+
StatementKind::Nop => {}
476438

477-
struct RemoveStatements<'a, 'tcx> {
478-
used_locals: &'a mut IndexVec<Local, usize>,
479-
arg_count: usize,
480-
tcx: TyCtxt<'tcx>,
481-
modified: bool,
482-
}
439+
StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}
483440

484-
impl<'a, 'tcx> RemoveStatements<'a, 'tcx> {
485-
fn new(
486-
used_locals: &'a mut IndexVec<Local, usize>,
487-
arg_count: usize,
488-
tcx: TyCtxt<'tcx>,
489-
) -> Self {
490-
Self { used_locals, arg_count, tcx, modified: false }
491-
}
441+
StatementKind::Assign(box (ref place, ref rvalue)) => {
442+
self.visit_lhs(place, location);
443+
self.visit_rvalue(rvalue, location);
444+
}
492445

493-
fn keep_local(&self, l: Local) -> bool {
494-
trace!("keep_local({:?}): count: {:?}", l, self.used_locals[l]);
495-
l.as_usize() <= self.arg_count || self.used_locals[l] != 0
446+
StatementKind::SetDiscriminant { ref place, variant_index: _ } => {
447+
self.visit_lhs(place, location);
448+
}
449+
}
496450
}
497-
}
498451

499-
impl<'a, 'tcx> MutVisitor<'tcx> for RemoveStatements<'a, 'tcx> {
500-
fn tcx(&self) -> TyCtxt<'tcx> {
501-
self.tcx
452+
fn visit_local(&mut self, local: &Local, _ctx: PlaceContext, _location: Location) {
453+
if self.increment {
454+
self.use_count[*local] += 1;
455+
} else {
456+
assert_ne!(self.use_count[*local], 0);
457+
self.use_count[*local] -= 1;
458+
}
502459
}
460+
}
503461

504-
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) {
505-
// Remove unnecessary StorageLive and StorageDead annotations.
506-
let mut i = 0usize;
507-
data.statements.retain(|stmt| {
508-
let keep = match &stmt.kind {
509-
StatementKind::StorageLive(l) | StatementKind::StorageDead(l) => {
510-
self.keep_local(*l)
511-
}
512-
StatementKind::Assign(box (place, _)) => self.keep_local(place.local),
513-
_ => true,
514-
};
515-
516-
if !keep {
517-
trace!("removing statement {:?}", stmt);
518-
self.modified = true;
519-
520-
let mut visitor = StatementDeclMarker::new(self.used_locals, stmt);
521-
visitor.visit_statement(stmt, Location { block, statement_index: i });
522-
}
462+
/// Removes unused definitions. Updates the used locals to reflect the changes made.
463+
fn remove_unused_definitions<'a, 'tcx>(used_locals: &'a mut UsedLocals, body: &mut Body<'tcx>) {
464+
// The use counts are updated as we remove the statements. A local might become unused
465+
// during the retain operation, leading to a temporary inconsistency (storage statements or
466+
// definitions referencing the local might remain). For correctness it is crucial that this
467+
// computation reaches a fixed point.
468+
469+
let mut modified = true;
470+
while modified {
471+
modified = false;
472+
473+
for data in body.basic_blocks_mut() {
474+
// Remove unnecessary StorageLive and StorageDead annotations.
475+
data.statements.retain(|statement| {
476+
let keep = match &statement.kind {
477+
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
478+
used_locals.is_used(*local)
479+
}
480+
StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local),
523481

524-
i += 1;
482+
StatementKind::SetDiscriminant { ref place, .. } => {
483+
used_locals.is_used(place.local)
484+
}
485+
_ => true,
486+
};
525487

526-
keep
527-
});
488+
if !keep {
489+
trace!("removing statement {:?}", statement);
490+
modified = true;
491+
used_locals.statement_removed(statement);
492+
}
528493

529-
self.super_basic_block_data(block, data);
494+
keep
495+
});
496+
}
530497
}
531498
}
532499

src/test/codegen-units/item-collection/instantiation-through-vtable.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// ignore-tidy-linelength
2-
// compile-flags:-Zprint-mono-items=eager
3-
// compile-flags:-Zinline-in-all-cgus
2+
// compile-flags:-Zprint-mono-items=eager -Zinline-in-all-cgus -Zmir-opt-level=0
43

54
#![deny(dead_code)]
65
#![feature(start)]

src/test/codegen/lifetime_start_end.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -O -C no-prepopulate-passes
1+
// compile-flags: -O -C no-prepopulate-passes -Zmir-opt-level=0
22

33
#![crate_type = "lib"]
44

@@ -18,10 +18,10 @@ pub fn test() {
1818
// CHECK: [[S_b:%[0-9]+]] = bitcast { i32, i32 }** %b to i8*
1919
// CHECK: call void @llvm.lifetime.start{{.*}}(i{{[0-9 ]+}}, i8* [[S_b]])
2020

21-
// CHECK: [[S__4:%[0-9]+]] = bitcast { i32, i32 }* %_4 to i8*
21+
// CHECK: [[S__4:%[0-9]+]] = bitcast { i32, i32 }* %_5 to i8*
2222
// CHECK: call void @llvm.lifetime.start{{.*}}(i{{[0-9 ]+}}, i8* [[S__4]])
2323

24-
// CHECK: [[E__4:%[0-9]+]] = bitcast { i32, i32 }* %_4 to i8*
24+
// CHECK: [[E__4:%[0-9]+]] = bitcast { i32, i32 }* %_5 to i8*
2525
// CHECK: call void @llvm.lifetime.end{{.*}}(i{{[0-9 ]+}}, i8* [[E__4]])
2626

2727
// CHECK: [[E_b:%[0-9]+]] = bitcast { i32, i32 }** %b to i8*

src/test/codegen/loads.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -C no-prepopulate-passes
1+
// compile-flags: -C no-prepopulate-passes -Zmir-opt-level=0
22

33
#![crate_type = "lib"]
44

src/test/codegen/naked-functions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -C no-prepopulate-passes
1+
// compile-flags: -C no-prepopulate-passes -Zmir-opt-level=0
22

33
#![crate_type = "lib"]
44
#![feature(naked_functions)]

src/test/codegen/refs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -C no-prepopulate-passes
1+
// compile-flags: -C no-prepopulate-passes -Zmir-opt-level=0
22

33
#![crate_type = "lib"]
44

src/test/incremental/hashes/closure_expressions.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
// build-pass (FIXME(62277): could be check-pass?)
99
// revisions: cfail1 cfail2 cfail3
10-
// compile-flags: -Z query-dep-graph -Zincremental-ignore-spans
10+
// compile-flags: -Z query-dep-graph -Zincremental-ignore-spans -Zmir-opt-level=0
1111

1212
#![allow(warnings)]
1313
#![feature(rustc_attrs)]
@@ -53,7 +53,7 @@ pub fn change_parameter_pattern() {
5353
}
5454

5555
#[cfg(not(cfail1))]
56-
#[rustc_clean(cfg="cfail2", except="hir_owner_nodes, typeck")]
56+
#[rustc_clean(cfg="cfail2", except="hir_owner_nodes, typeck, optimized_mir")]
5757
#[rustc_clean(cfg="cfail3")]
5858
pub fn change_parameter_pattern() {
5959
let _ = |(x,): (u32,)| x;

src/test/incremental/hashes/enum_constructors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
// build-pass (FIXME(62277): could be check-pass?)
99
// revisions: cfail1 cfail2 cfail3
10-
// compile-flags: -Z query-dep-graph -Zincremental-ignore-spans
10+
// compile-flags: -Z query-dep-graph -Zincremental-ignore-spans -Zmir-opt-level=0
1111

1212
#![allow(warnings)]
1313
#![feature(rustc_attrs)]

0 commit comments

Comments
 (0)