Skip to content

Commit a6b64be

Browse files
committed
simplify-locals: Unify use count visitors
The simplify locals implementation uses two different visitors to update the locals use counts. The DeclMarker calculates the initial use counts. The StatementDeclMarker updates the use counts as statements are being removed from the block. Replace them with a single visitor that can operate in either mode, ensuring consistency of behaviour. Additionally use exhaustive match to clarify what is being optimized. No functional changes intended.
1 parent 1126953 commit a6b64be

File tree

1 file changed

+111
-113
lines changed

1 file changed

+111
-113
lines changed

compiler/rustc_mir/src/transform/simplify.rs

Lines changed: 111 additions & 113 deletions
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,23 +323,15 @@ 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.
340333
loop {
341-
let mut remove_statements = RemoveStatements::new(&mut used_locals, arg_count, tcx);
334+
let mut remove_statements = RemoveStatements::new(&mut used_locals, tcx);
342335
remove_statements.visit_body(body);
343336

344337
if !remove_statements.modified {
@@ -347,7 +340,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyLocals {
347340
}
348341

349342
// 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);
343+
let map = make_local_map(&mut body.local_decls, &used_locals);
351344

352345
// Only bother running the `LocalUpdater` if we actually found locals to remove.
353346
if map.iter().any(Option::is_none) {
@@ -363,14 +356,14 @@ impl<'tcx> MirPass<'tcx> for SimplifyLocals {
363356
/// Construct the mapping while swapping out unused stuff out from the `vec`.
364357
fn make_local_map<V>(
365358
local_decls: &mut IndexVec<Local, V>,
366-
used_locals: IndexVec<Local, u32>,
367-
arg_count: usize,
359+
used_locals: &UsedLocals,
368360
) -> IndexVec<Local, Option<Local>> {
369361
let mut map: IndexVec<Local, Option<Local>> = IndexVec::from_elem(None, &*local_decls);
370362
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 {
363+
364+
for alive_index in local_decls.indices() {
365+
// `is_used` treats the `RETURN_PLACE` and arguments as used.
366+
if !used_locals.is_used(alive_index) {
374367
continue;
375368
}
376369

@@ -384,115 +377,125 @@ fn make_local_map<V>(
384377
map
385378
}
386379

387-
struct DeclMarker<'a, 'tcx> {
388-
pub local_counts: IndexVec<Local, u32>,
389-
pub body: &'a Body<'tcx>,
380+
/// Keeps track of used & unused locals.
381+
struct UsedLocals {
382+
increment: bool,
383+
arg_count: u32,
384+
use_count: IndexVec<Local, u32>,
390385
}
391386

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 }
387+
impl UsedLocals {
388+
/// Determines which locals are used & unused in the given body.
389+
fn new(body: &Body<'_>) -> Self {
390+
let mut this = Self {
391+
increment: true,
392+
arg_count: body.arg_count.try_into().unwrap(),
393+
use_count: IndexVec::from_elem(0, &body.local_decls),
394+
};
395+
this.visit_body(body);
396+
this
395397
}
396-
}
397398

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-
}
399+
/// Checks if local is used.
400+
///
401+
/// Return place and arguments are always considered used.
402+
fn is_used(&self, local: Local) -> bool {
403+
trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]);
404+
local.as_u32() <= self.arg_count || self.use_count[local] != 0
405+
}
406406

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-
}
407+
/// Updates the use counts to reflect the removal of given statement.
408+
fn statement_removed(&mut self, statement: &Statement<'tcx>) {
409+
self.increment = false;
441410

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

451-
impl<'a, 'tcx> StatementDeclMarker<'a, 'tcx> {
452-
pub fn new(
453-
used_locals: &'a mut IndexVec<Local, u32>,
454-
statement: &'a Statement<'tcx>,
455-
) -> Self {
456-
Self { used_locals, statement }
416+
/// Visits a left-hand side of an assignment.
417+
fn visit_lhs(&mut self, place: &Place<'tcx>, location: Location) {
418+
if place.is_indirect() {
419+
// A use, not a definition.
420+
self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), location);
421+
} else {
422+
// A definition. Although, it still might use other locals for indexing.
423+
self.super_projection(
424+
place.local,
425+
&place.projection,
426+
PlaceContext::MutatingUse(MutatingUseContext::Projection),
427+
location,
428+
);
429+
}
457430
}
458431
}
459432

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;
433+
impl Visitor<'_> for UsedLocals {
434+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
435+
match statement.kind {
436+
StatementKind::LlvmInlineAsm(..)
437+
| StatementKind::SetDiscriminant { .. } // FIXME: Try to remove those as well.
438+
| StatementKind::Retag(..)
439+
| StatementKind::Coverage(..)
440+
| StatementKind::FakeRead(..)
441+
| StatementKind::AscribeUserType(..) => {
442+
self.super_statement(statement, location);
443+
}
444+
445+
StatementKind::Nop => {}
446+
447+
StatementKind::StorageLive(_local) | StatementKind::StorageDead(_local) => {}
448+
449+
StatementKind::Assign(box (ref place, ref rvalue)) => {
450+
let can_skip = match rvalue {
451+
Rvalue::Use(_)
452+
| Rvalue::Discriminant(_)
453+
| Rvalue::BinaryOp(_, _, _)
454+
| Rvalue::CheckedBinaryOp(_, _, _)
455+
| Rvalue::Repeat(_, _)
456+
| Rvalue::AddressOf(_, _)
457+
| Rvalue::Len(_)
458+
| Rvalue::UnaryOp(_, _)
459+
| Rvalue::Aggregate(_, _) => true,
460+
461+
Rvalue::Ref(..)
462+
| Rvalue::ThreadLocalRef(..)
463+
| Rvalue::Cast(..)
464+
| Rvalue::NullaryOp(..) => false,
465+
};
466+
if can_skip {
467+
self.visit_lhs(place, location);
468+
} else {
469+
self.visit_place(
470+
place,
471+
PlaceContext::MutatingUse(MutatingUseContext::Store),
472+
location,
473+
);
474+
}
475+
self.visit_rvalue(rvalue, location);
466476
}
467477
}
478+
}
468479

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;
480+
fn visit_local(&mut self, local: &Local, _ctx: PlaceContext, _location: Location) {
481+
if self.increment {
482+
self.use_count[*local] += 1;
483+
} else {
484+
assert_ne!(self.use_count[*local], 0);
485+
self.use_count[*local] -= 1;
473486
}
474487
}
475488
}
476489

477490
struct RemoveStatements<'a, 'tcx> {
478-
used_locals: &'a mut IndexVec<Local, u32>,
479-
arg_count: usize,
491+
used_locals: &'a mut UsedLocals,
480492
tcx: TyCtxt<'tcx>,
481493
modified: bool,
482494
}
483495

484496
impl<'a, 'tcx> RemoveStatements<'a, 'tcx> {
485-
fn new(
486-
used_locals: &'a mut IndexVec<Local, u32>,
487-
arg_count: usize,
488-
tcx: TyCtxt<'tcx>,
489-
) -> Self {
490-
Self { used_locals, arg_count, tcx, modified: false }
491-
}
492-
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
497+
fn new(used_locals: &'a mut UsedLocals, tcx: TyCtxt<'tcx>) -> Self {
498+
Self { used_locals, tcx, modified: false }
496499
}
497500
}
498501

@@ -503,26 +506,21 @@ impl<'a, 'tcx> MutVisitor<'tcx> for RemoveStatements<'a, 'tcx> {
503506

504507
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) {
505508
// 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)
509+
data.statements.retain(|statement| {
510+
let keep = match &statement.kind {
511+
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
512+
self.used_locals.is_used(*local)
511513
}
512-
StatementKind::Assign(box (place, _)) => self.keep_local(place.local),
514+
StatementKind::Assign(box (place, _)) => self.used_locals.is_used(place.local),
513515
_ => true,
514516
};
515517

516518
if !keep {
517-
trace!("removing statement {:?}", stmt);
519+
trace!("removing statement {:?}", statement);
518520
self.modified = true;
519-
520-
let mut visitor = StatementDeclMarker::new(self.used_locals, stmt);
521-
visitor.visit_statement(stmt, Location { block, statement_index: i });
521+
self.used_locals.statement_removed(statement);
522522
}
523523

524-
i += 1;
525-
526524
keep
527525
});
528526

0 commit comments

Comments
 (0)