[−][src]Module rustc_mir::transform::dest_prop

Propagates assignment destinations backwards in the CFG to eliminate redundant assignments.

Motivation

MIR building can insert a lot of redundant copies, and Rust code in general often tends to move values around a lot. The result is a lot of assignments of the form dest = {move} src; in MIR. MIR building for constants in particular tends to create additional locals that are only used inside a single block to shuffle a value around unnecessarily.

LLVM by itself is not good enough at eliminating these redundant copies (eg. see https://github.com/rust-lang/rust/issues/32966), so this leaves some performance on the table that we can regain by implementing an optimization for removing these assign statements in rustc itself. When this optimization runs fast enough, it can also speed up the constant evaluation and code generation phases of rustc due to the reduced number of statements and locals.

The Optimization

Conceptually, this optimization is "destination propagation". It is similar to the Named Return Value Optimization, or NRVO, known from the C++ world, except that it isn't limited to return values or the return place _0. On a very high level, independent of the actual implementation details, it does the following:

  1. Identify dest = src; statements that can be soundly eliminated.
  2. Replace all mentions of src with dest ("unifying" them and propagating the destination backwards).
  3. Delete the dest = src; statement (by making it a nop).

Step 1) is by far the hardest, so it is explained in more detail below.

Soundness

Given an Assign statement dest = src;, where dest is a Place and src is an Rvalue, there are a few requirements that must hold for the optimization to be sound:

Here, the first two conditions are simple structural requirements on the Assign statements that can be trivially checked. The liveness requirement however is more difficult and costly to check.

Previous Work

A previous attempt at implementing an optimization like this turned out to be a significant regression in compiler performance. Fixing the regressions introduced a lot of undesirable complexity to the implementation.

A subsequent approach tried to avoid the costly computation by limiting itself to acyclic CFGs, but still turned out to be far too costly to run due to suboptimal performance within individual basic blocks, requiring a walk across the entire block for every assignment found within the block. For the tuple-stress benchmark, which has 458745 statements in a single block, this proved to be far too costly.

Since the first attempt at this, the compiler has improved dramatically, and new analysis frameworks have been added that should make this approach viable without requiring a limited approach that only works for some classes of CFGs:

Also, rustc now has a simple NRVO pass (see nrvo.rs), which handles a subset of the cases that this destination propagation pass handles, proving that similar optimizations can be performed on MIR.

Pre/Post Optimization

It is recommended to run SimplifyCfg and then SimplifyLocals some time after this pass, as it replaces the eliminated assign statements with nops and leaves unused locals behind.

Structs

BorrowCollector
CandidateAssignment

A dest = {move} src; statement at loc.

Conflicts
DestinationPropagation
FindAssignments
IndexCollector
Replacements
Replacer
UnifyLocal

Constants

MAX_BLOCKS
MAX_LOCALS

Functions

ever_borrowed_locals

Walks MIR to find all locals that have their address taken anywhere.

find_candidates

Scans the MIR for assignments between locals that we might want to consider merging.

is_local_required

Some locals are part of the function's interface and can not be removed.

locals_used_as_array_index

PlaceElem::Index only stores a Local, so we can't replace that with a full Place.