-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Liveness: control flow analysis and other goodies #15355
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
Conversation
9c2c5a3
to
4a4c1ed
Compare
This is a partial rewrite of Liveness, so has some other notable changes: - A proper multi-pass system to prevent code duplication - Better logging - Minor bugfixes
This is useful for debug printing air when liveness is broken.
This code only runs in a debug zig compiler, similar to verifying llvm modules.
`try`, `try_ptr`, and `block` now have extra payloads.
…s as used Backends want to avoid emitting unused instructions which do not have side effects: to that end, they all have `Liveness.isUnused` checks for many instructions. However, checking this in the backends avoids a lot of potential optimizations. For instance, if a nested field is loaded, then the first field access would still be emitted, since its result is used by the next access (which is then unreferenced). To elide more instructions, Liveness can track this data instead. For operands which do not have to be lowered (i.e. are not side effecting and are not something special like `arg), Liveness can ignore their operand usages, and push the unused information further up, potentially marking many more instructions as unreferenced. In doing this, I also uncovered a bug in the LLVM backend relating to discarding the result of `@cVaArg`, which this change fixes. A behaviour test has been added to cover it.
4a4c1ed
to
dab50b3
Compare
Uses the new liveness behaviour. This also removes useless calls to `processDeath` on branches that were just initialized. Branch consolidation and processing deaths on branches inside `condbr` is still a TODO, just like before. This also skips var_args on other native backends as they do not support this feature yet.
These backends doesn't support the new liveness yet.
The self-hosted aarch64 backend is not currently functional due to the Liveness changes. A previous commit disabled aarch64 on the behavior tests; this commit disables it and arm for the test cases. Moreover, all incremental test cases have been unified into shared cross-platform cases, which can be gradually enabled as the backends improve.
dab50b3
to
b3f9fe6
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.
Great work. I like the way you structured Liveness.zig with the two passes.
I had a minimal amount of review comments, but they are not merge blockers, and given that we are looking at a green CI already, I'm going to hit the merge button.
/// Returns whether the given instruction must always be lowered, for instance because it can cause | ||
/// side effects. If an instruction does not need to be lowered, and Liveness determines its result | ||
/// is unused, backends should avoid lowering it. | ||
pub fn mustLower(air: Air, inst: Air.Inst.Index) bool { |
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.
Suggested rename:
pub fn mustLower(air: Air, inst: Air.Inst.Index) bool { | |
pub fn hasSideEffects(air: Air, inst: Air.Inst.Index) bool { |
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 actually originally did call it that, but when discussing it with jacobly we concluded that name was misleading - there are some instructions, such as arg
and prefetch
, which don't have "side effects" in any meaningful way, but must always be lowered nonetheless
@@ -0,0 +1,610 @@ | |||
//! Verifies that liveness information is valid. |
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.
What a delightful addition. In the future, it would be nice to have an AIR verification pass as well. In particular, it could catch the problem of an alloc instruction in the wrong scope.
@@ -8314,8 +8177,7 @@ pub const FuncGen = struct { | |||
} | |||
|
|||
fn airRetAddr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { | |||
if (self.liveness.isUnused(inst)) return null; | |||
|
|||
_ = inst; |
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.
instead, would be better to delete the parameter
What we have here is basically a complete rewrite of Liveness! Here's the rundown.
Liveness as it stands today isn't actually great at its job, because it doesn't consider control flow properly. Any body must end with a return-esque instruction or a
br
, but Liveness ignores that completely and doesn't kill values until you reach the last "syntactic" use. That means a lot of values live longer than they actually need to.This rewrite of Liveness makes it control flow aware. Instructions may now have arbitrarily many deaths marked, one per control flow branch, and every value should die as soon as is possible in each branch. I managed to actually keep Liveness as a two-pass system (I briefly thought I'd need 3 or even 4!), although they're totally different to what exists in master today. I did generalise the pass system, just to avoid a huge block of redundant code.
We also have a couple of other changes to Liveness. Firstly, a small one: we don't track liveness data for constants anymore. This data almost certainly isn't useful for any backends, so omitting it comes at no cost and decreases the size of Liveness extra data. The other change is a bit bigger, and is related to eliding instructions.
Consider the following AIR:
Backends try to detect instructions which can be elided by checking
Liveness.isUnused
for instructions such asstruct_field_ptr
which don't have side effects. The thing is, in the example above, even if%4
is unused, that will only allow%4
itself to be elided:%2
and%3
cannot be because they are not unused (they're needed for the next instruction in sequence!). This PR makes Liveness aware of "non-side-effecting" instructions like this: when traversing through the AIR, if Liveness encounters an instruction which is unused and doesn't have to be lowered if not needed, it skips analysis of its operands. This means%2
-%4
all get marked as unused in the above example. Then, backends just need to doif (liveness.isUnused(inst) and !air.mustLower(inst)) continue;
in their main instruction processing loop, which also avoidsif (isUnused(inst))
boilerplate in a bunch of AIR handler functions. In doing this, I found a LLVM bug with ignoring the result of@cVaArg
, which this change fixed - I added a corresponding test case.Anyways, onto actual results! @jacobly0's work on x86_64 here has gotten the full test runner (with the server) working, so we have actual communication with the build system:
Overall, this PR should hopefully allow backends to emit much more optimal code. The work on instruction eliding also might have a small impact on LLVM emit times due to fewer AIR instructions being lowered, but I've not tested that.