-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Simplify switches on a statically known discriminant in MIR. #112688
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
Simplify switches on a statically known discriminant in MIR. #112688
Conversation
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
☔ The latest upstream changes (presumably #112346) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have SeparateConstSwitch
and ConstProp
, which taken together do a similar transformation. What is the gain of this pass compared to the current state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debug!(body = %tcx.def_path_str(body.source.def_id())); | |
debug!(body = ?body.source.def_id()); |
def_path_str
dooms the current compilation session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let predecessors: &IndexSlice<_, _> = body.basic_blocks.predecessors(); | |
let predecessors = body.basic_blocks.predecessors(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let predecessors: &[BasicBlock] = &predecessors[block]; | |
let predecessors = &predecessors[block]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this particular order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it might be better than iterating normally but i have switched it back to just iter_enumerated
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"{pred:?}: {place:?} = {}::{variant:?}; goto -> {block:?};", | |
tcx.def_path_str(def_id) | |
"{pred:?}: {place:?} = {def_id:?}::{variant:?}; goto -> {block:?};" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You must take the substitutions into account here, otherwise you'll get the wrong type.
@rustbot author |
I'd say the main advantage is that this pass sort of combines the effect of |
@rustbot ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is unrelated. Could you submit it as a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i removed this for now, i'll open a PR soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i also removed this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn name(&self) -> &'static str { | |
"SimplifyStaticSwitch" | |
} |
This is the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what you call discr
earlier, could you name it so here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
block
here is pred
, please call it so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let old_goto = &mut data.terminator.as_mut().unwrap().kind; | |
let old_goto = &mut data.terminator_mut().kind; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pass clones statements. As we have SSA-based passes, and we don't try to recover SSAness, it should be run much later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i have moved the pass to right before SeparateConstSwitch
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those checks should happen outside of the find_map
, in cases there are several assignments to switched
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i also moved these checks to outside of the find_map
.
@rustbot author |
@rustbot ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This is starting to look good.
Could you add comments explaining the pattern you are looking for in this opt, and why it is sound?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we just pass a range to this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A simple Vec
should be enough, as you don't use it for indexing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to check if discr
is mutated between this assignment and the end of block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a check for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this can be an if-let.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document why there is only a single pred -> block
edge in the whole CFG, and that no earlier threading has overwritten it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may also be useful to document why pred != block
and assert it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should assert that old_goto
is indeed goto block
, to ensure we have not overwritten it in the mean time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added an assert to check this, as well as not allowing two StaticSwitch
s to have the same pred
, so the terminator should not get overwritten.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use IndexSlice::pick2_mut
to avoid having to clone statements twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cool, i also added this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add more tests?
With all the extra checks I made you add + weird cfgs if possible.
See #107009 for a few examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a few more tests, including checks for mutation/borrowing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we prevent threading through a loop header, in order to avoid creating irreducible CFGs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also added a check for this.
@rustbot author |
Thank you for reviewing this! |
@rustbot ready |
This comment has been minimized.
This comment has been minimized.
removes unnecessary switches on statically known discriminants.
.statements | ||
.iter() | ||
.enumerate() | ||
.take_while(|&(index, _)| index != statement_index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.statements | |
.iter() | |
.enumerate() | |
.take_while(|&(index, _)| index != statement_index) | |
.statements[..statement_index] | |
.iter() | |
.enumerate() |
continue | ||
}; | ||
|
||
if place.local != switched { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
local
has necessarily an integer type. Should we assert that place.projection
is empty?
continue; | ||
} | ||
|
||
let mut finder = MutatedLocalFinder { local: discr, mutated: false }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be created inside the loop, to ensure we don't forget to reset mutated
.
| StatementKind::Assign(box ( | ||
place, | ||
Rvalue::Aggregate(box AggregateKind::Adt(_, variant, ..), ..), | ||
)) if place.local == discr => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert place.projection
is empty here too.
|
||
continue 'preds; | ||
} | ||
_ => finder.visit_statement(statement, Location { block, statement_index }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we create finder
, visit, and check mutated
in this arm, instead of the other arms?
Some(exclude) => { | ||
pred.statements.extend(block.statements.iter().enumerate().filter_map( | ||
|(index, statement)| { | ||
if index == exclude { None } else { Some(statement.clone()) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not necessary. We can run remove_unused_definitions
together with remove_dead_blocks
.
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _: Location) { | ||
if self.local == place.local && let PlaceContext::MutatingUse(..) = context { | ||
self.mutated = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we detect storage statements too?
StorageLive(_11); | ||
_9 = discriminant(_1); | ||
switchInt(move _9) -> [0: bb7, 1: bb5, otherwise: bb6]; | ||
switchInt(move _9) -> [0: bb5, 1: bb3, otherwise: bb4]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make this a unit test for SeparateConstSwitch
?
|
||
// We use the SSA, to destroy the SSA. | ||
let data = { | ||
let (block, pred) = basic_blocks.pick2_mut(block, switch.pred); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment explaining why block != switch.pred
?
} | ||
|
||
let basic_blocks = body.basic_blocks.as_mut(); | ||
let num_switches: usize = static_switches.iter().map(|(_, switches)| switches.len()).sum(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Counting the number of opts just for tracing is not useful.
☔ The latest upstream changes (presumably #109025) made this pull request unmergeable. Please resolve the merge conflicts. |
@JohnBobbo96 ping from triage - can you post your status on this PR? There hasn't been an update in a few months and there are merge conflicts Thanks! FYI: when a PR is ready for review, send a message containing |
@JohnBobbo96 Ping from triage: I'm closing this due to inactivity, Please reopen when you are ready to continue with this. @rustbot label: +S-inactive |
This PR adds a new MIR pass:
SimplifyStaticSwitch
, which aims to simplify switches in MIR that match on a known discriminant, this mainly happens as a result of MIR inlining.Addresses: #111442 (comment)