-
Notifications
You must be signed in to change notification settings - Fork 8
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 control flow for CFGs generated by eggcc. #610
Conversation
Question for reviewers: this will remove more branches if we iterate to a fixed point. It's easy to add; let me know if you want to try 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.
Really great job, excited that this is working!
I think some of the gnarly checks that actually do the graph rewrites can be cleaned up. The actual analysis seems pretty clean.
#[derive(Clone, Default, Debug)] | ||
enum ValueInfo { | ||
#[default] | ||
Bot, |
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.
Worth commenting intuition about Bot
and Top
. I think Bot
is "knows nothing" and Top
is unreachable because we found a conflict
Transform, | ||
)>, | ||
/// The set of variables written to in this basic block. | ||
kills: HashSet<Identifier>, |
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.
As far as I can tell this is the variables written to in transforms
. Could we just initialize it after step 2?
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 can, but then if we added more calls to add_assignment
later, we'd then have to remember to recompute this. I think it's a bit less brittle to keep something like the current structure.
src/rvsdg/simplify_branches.rs
Outdated
continue; | ||
}; | ||
let Some(val) = to_lit(&val) else { | ||
continue; |
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.
If we keep the continue
and break
statements, might be worth labeling the while loops
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.
When I see labelled while loops I generally assume that some code is going to do a multi-level break... I think the continues and breaks here are pretty clear? Are we doing this elsewhere in the code?
src/rvsdg/simplify_branches.rs
Outdated
let Some(val) = to_lit(&val) else { | ||
continue; | ||
}; | ||
if val_analysis.data[admin_node].kills.contains(&arg) { |
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 think this if
is a special, easy case- can it be broken out into a helper? Or a whole different pass perhaps?
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.
The thing that makes that harder is the break
vs. continue
case. We can lift that into its own type, but that + a bunch of params creates a bit of an odd helper. I think this might be better but I'm not sure.
As for a separate pass I think that'd require that we redo a bunch of the scaffolding for the while loops? Not sure if that's worth it either.
enum Next { Break, Continue }
fn fold_constant_branch(
&mut self,
admin_node: NodeIndex,
succ: NodeIndex,
arg: &Identifier,
val_analysis: &ValueAnalysis,
val: &Literal,
) -> (Next, bool) {
if succ != self.exit && self.graph.neighbors(admin_node).any(|x| x == self.exit) {
// Don't remove any outgoing links to the exit node.
return (Next::Break, false);
}
// We assign to the branched-on argument in the admin
// node. See if we can fold the constant branch here.
let ValueInfo::Known(lit) = val_analysis.data[&admin_node].get_output(arg) else {
return (Next::Continue, false);
};
if &lit != val {
return (Next::Continue, false);
}
// okay, we have found a matching edge. Replace this branch
// with a jump.
let mut walker = self
.graph
.neighbors_directed(admin_node, Direction::Outgoing)
.detach();
while let Some((outgoing, _)) = walker.next(&self.graph) {
self.graph.remove_edge(outgoing);
}
self.graph.add_edge(
admin_node,
succ,
Branch {
op: BranchOp::Jmp,
pos: None,
},
);
// Don't run the rest of the inner loop.
(Next::Break, 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.
I agree the helper is weird
I still think a separate while loop may be cleaner but I'm not hung up on this
src/rvsdg/simplify_branches.rs
Outdated
// checked that the footer was empty when we populated | ||
// admin_nodes. We do this because we more or less don't | ||
// use footers on our way back to bril. | ||
scratch.extend(self.graph[*admin_node].instrs.iter().cloned()); |
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.
Not sure I understand how scratch
is getting used. Can scratch
be a local definition here? What about just above this while loop instead of far up?
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.
scratch is being used to save repeated allocations when running the algorithm, so the higher up the better I think.
I've moved it inside rewrite_branches
though.
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.
Oh I see.
I think scratch
is a bad name, and I don't care about the repeated allocations.
Also update the snapshots post-rebase
b975e01
to
41fae5f
Compare
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.
Really great improvement! This looks ready to merge, it's challenging to refactor further as we've seen from the threads
There's a fairly detailed doc comment in
simplify_branches.rs
. The goal here is to clean up the CFG output and remove redundant branches from the generated code. Here's an example: at head, thesqrt
benchmark generates the following cfg:And with this PR, we get: