Simplify switches on a statically known discriminant in MIR.#112688
Simplify switches on a statically known discriminant in MIR.#112688nullptrderef29 wants to merge 1 commit intorust-lang:masterfrom nullptrderef29:simplify_static_switch
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. |
cjgillot
left a comment
There was a problem hiding this comment.
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.
| 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.
| let predecessors: &IndexSlice<_, _> = body.basic_blocks.predecessors(); | |
| let predecessors = body.basic_blocks.predecessors(); |
There was a problem hiding this comment.
| let predecessors: &[BasicBlock] = &predecessors[block]; | |
| let predecessors = &predecessors[block]; |
There was a problem hiding this comment.
Why this particular order?
There was a problem hiding this comment.
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.
| "{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.
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.
This change is unrelated. Could you submit it as a separate PR?
There was a problem hiding this comment.
i removed this for now, i'll open a PR soon.
There was a problem hiding this comment.
i also removed this.
There was a problem hiding this comment.
| fn name(&self) -> &'static str { | |
| "SimplifyStaticSwitch" | |
| } |
This is the default.
There was a problem hiding this comment.
This is what you call discr earlier, could you name it so here?
There was a problem hiding this comment.
block here is pred, please call it so.
There was a problem hiding this comment.
| 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.
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.
i have moved the pass to right before SeparateConstSwitch.
There was a problem hiding this comment.
Those checks should happen outside of the find_map, in cases there are several assignments to switched.
There was a problem hiding this comment.
i also moved these checks to outside of the find_map.
|
@rustbot author |
|
@rustbot ready |
cjgillot
left a comment
There was a problem hiding this comment.
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.
Nit: can we just pass a range to this function?
There was a problem hiding this comment.
A simple Vec should be enough, as you don't use it for indexing.
There was a problem hiding this comment.
You need to check if discr is mutated between this assignment and the end of block.
There was a problem hiding this comment.
I have added a check for this.
There was a problem hiding this comment.
Nit: this can be an if-let.
There was a problem hiding this comment.
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.
It may also be useful to document why pred != block and assert it.
There was a problem hiding this comment.
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.
I have added an assert to check this, as well as not allowing two StaticSwitchs to have the same pred, so the terminator should not get overwritten.
There was a problem hiding this comment.
Could we use IndexSlice::pick2_mut to avoid having to clone statements twice?
There was a problem hiding this comment.
This is cool, i also added this.
There was a problem hiding this comment.
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.
I have added a few more tests, including checks for mutation/borrowing.
There was a problem hiding this comment.
Should we prevent threading through a loop header, in order to avoid creating irreducible CFGs?
There was a problem hiding this comment.
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.
| .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.
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.
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.
Assert place.projection is empty here too.
|
|
||
| continue 'preds; | ||
| } | ||
| _ => finder.visit_statement(statement, Location { block, statement_index }), |
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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)