Skip to content

Add a MIR pass to simplify the control flow graph #29757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 12, 2015

Conversation

dotdash
Copy link
Contributor

@dotdash dotdash commented Nov 10, 2015

For now, this pass does some easy transformations, like eliminating
empty blocks that just jump to another block, some trivial
conversion of If terminators into Gotos and removal of dead blocks.

r? @nikomatsakis

@dotdash dotdash force-pushed the mir_simplify_cfg branch 2 times, most recently from cc76611 to 099e32c Compare November 10, 2015 20:43

impl SimplifyCFG {
fn remove_dead_blocks(&self, mir: &mut Mir) {
let mut seen: Vec<_> = mir.basic_blocks.iter().map(|_| false).collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be vec![false; mir.basic_blocks.len()].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I always forget about that form of vec! for some reason, thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this use a BitSet?

@Manishearth
Copy link
Member

It might be useful to make MirPasses something we can hook into via plugin_registrar (quite an easy change to make, just maintain a vec of Box<MirPass>). GCC lets people insert GIMPLE/ipa/etc passes. Clang lets you insert clang passes as well as LLVM passes (iirc).

It would also make it easy to work on experimental passes out-of-tree and without long builds.

@dotdash
Copy link
Contributor Author

dotdash commented Nov 11, 2015

@Manishearth I intent to add some kind of pass manager as a follow-up. That would allow to add passes dynamically and also provide means to store some analysis results across passes, like a predecessor map which would stay valid across passes that don't modify the graph structure.

@huonw
Copy link
Member

huonw commented Nov 11, 2015

How expensive is this? (i.e. does this need to obey opt-level?)

(I imagine not, and I guess it may even improve compile times in particularly complicated cases by reducing the interactions with LLVM (maybe?), but seems worth considering.)

@dotdash
Copy link
Contributor Author

dotdash commented Nov 11, 2015

@huonw It seems pretty cheap. Testing a function that has 1000 times this:

if x > 0 { let _y = 0 } else { let _y = 0 }

The pass can't optimize those because of the let statements, and there's no time change for the MIR dump, both with and without the pass, it takes about 11-12ms.

Using 1000 of this instead:

if x > 0 {} else {}

The pass can drop a ton of empty blocks. Again, the MIR dump time is pretty much unchanged at 5ms, but since the MIR is reduced so much, LLVM passes drop from 16ms to 10ms. So a net win of 6ms.

And the MIR as we currently build it has so many empty blocks that we really should run this pass once even when optimizations are off.

use transform::util;
use transform::MIRPass;

pub struct SimplifyCFG;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Usually we provide a top-level fn like simplify_cfg::simplify(...) rather than calling a method on a singleton; not that I care very much. And yes the capitalization convention is SimplifyCfg (but I saw that mentioned elsewhere).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a pass manager is added later, this needs to be a singleton, as the relevant method is in the MirPass trait, and the pass manager would hold Box<MirPass>es.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll go with a new method on SimplifyCfg, and then call apply on its result.

@nikomatsakis
Copy link
Contributor

OK, this looks good to me, but there have been enough comments etc I'd like to take a look at the next revision before giving r+. So ping me when it's ready. :)

@nikomatsakis
Copy link
Contributor

@dotdash

I tried to keep the number of swaps proportional to the number of removed blocks, but that isn't worth that much, because I rewrite all targets anyway, and creating the reverse mapping likely eats up any time that has been saved by the reduced number of swaps. So I'll change this.

Yeah, after posting that comment I was thinking about how your version was O(D) where D is dead blocks (or at least the main loop is). Honestly I have no idea how the perf works out. I'm not sure which is better.

let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();

let mut dead = 0;
for i in 0..num_blocks {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it occurs to me that we could make this more efficient by doing:

let first_dead = match keep.iter().position(|&k| !k) {
    None => return,
    Some(first_dead) => first_dead,
};
let mut dead = 0;
let mut replacements: Vec<_> = (0..num_blocks).map(BasicBlock::new).collect();
for i in first_dead .. num_blocks {
    if keep[i] {
        replacements[i] = BasicBlock::new(i - dead);
        mir.basic_blocks.swap(i, i - dead);
    } else {
        dead += 1;
    }
}

For now, this pass does some easy transformations, like eliminating
empty blocks that just jump to another block, some trivial
conversion of If terminators into Gotos and removal of dead blocks.
@nikomatsakis
Copy link
Contributor

@bors r+

Nice! Obviously we'll have to figure out what to do w/ the GraphExtents, since this invalidates those. But that can come later. I'd like to change those anyhow.

@bors
Copy link
Collaborator

bors commented Nov 12, 2015

📌 Commit a4e5c0f has been approved by nikomatsakis

@bors
Copy link
Collaborator

bors commented Nov 12, 2015

⌛ Testing commit a4e5c0f with merge 098ea17...

bors added a commit that referenced this pull request Nov 12, 2015
For now, this pass does some easy transformations, like eliminating
empty blocks that just jump to another block, some trivial
conversion of If terminators into Gotos and removal of dead blocks.

r? @nikomatsakis
@bors bors merged commit a4e5c0f into rust-lang:master Nov 12, 2015
@dotdash dotdash deleted the mir_simplify_cfg branch January 15, 2016 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.