Skip to content

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

Merged
merged 18 commits into from
Apr 21, 2023

Conversation

mlugg
Copy link
Member

@mlugg mlugg commented Apr 19, 2023

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:

%1 = ...
%2 = struct_field_ptr(%1, 0)
%3 = struct_field_ptr(%2, 0)
%4 = struct_field_ptr(%3, 0)

Backends try to detect instructions which can be elided by checking Liveness.isUnused for instructions such as struct_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 do if (liveness.isUnused(inst) and !air.mustLower(inst)) continue; in their main instruction processing loop, which also avoids if (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:

image

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.

@mlugg mlugg force-pushed the feat/liveness-control-flow branch 11 times, most recently from 9c2c5a3 to 4a4c1ed Compare April 20, 2023 03:02
@mlugg mlugg mentioned this pull request Apr 20, 2023
mlugg and others added 5 commits April 20, 2023 20:28
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.
@mlugg mlugg force-pushed the feat/liveness-control-flow branch from 4a4c1ed to dab50b3 Compare April 20, 2023 19:29
mlugg and others added 12 commits April 20, 2023 20:49
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.
@mlugg mlugg force-pushed the feat/liveness-control-flow branch from dab50b3 to b3f9fe6 Compare April 20, 2023 19:49
Copy link
Member

@andrewrk andrewrk left a 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 {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested rename:

Suggested change
pub fn mustLower(air: Air, inst: Air.Inst.Index) bool {
pub fn hasSideEffects(air: Air, inst: Air.Inst.Index) bool {

Copy link
Member Author

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.
Copy link
Member

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;
Copy link
Member

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

@andrewrk andrewrk merged commit 528b66f into ziglang:master Apr 21, 2023
@mlugg mlugg deleted the feat/liveness-control-flow branch May 18, 2025 20:08
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.

4 participants