Skip to content

Cranelift: Rewrite conditional branches to unconditional traps into conditional traps during legalization #10988

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

fitzgen
Copy link
Member

@fitzgen fitzgen commented Jun 9, 2025

Given this instruction:

brif v0, block1, block2

If we know that block1 does nothing but immediately trap then we can rewrite
that brif into the following:

trapz v0, <trapcode>
jump block2

(And we can do the equivalent with trapz if block2 immediately traps).

This transformation allows for the conditional trap instructions to be GVN'd and
for our egraphs mid-end to generally better optimize the program. We
additionally have better codegen in our backends for trapz than branches to
unconditional traps.

Fixes #10941

fitzgen added 3 commits June 9, 2025 09:56
Note: the test expectation change in `filetests/egraph/misc.clif` is simply
because we happen to change the order in which we created the legalized
`stack_addr` instructions that get GVN'd together, and therefore also changed
their relative value numbering. We dedupe to the value that occurs first in the
function, which is the one inserted into the DFG *after* the other now because
of the backwards traversal, and it therefore has a different value number from
before. The resulting program is identical, modulo value numbering.
…onditional traps during legalization

Given this instruction:

```clif
brif v0, block1, block2
```

If we know that `block1` does nothing but immediately trap then we can rewrite
that `brif` into the following:

```clif
trapz v0, <trapcode>
jump block2
```

(And we can do the equivalent with `trapz` if `block2` immediately traps).

This transformation allows for the conditional trap instructions to be GVN'd and
for our egraphs mid-end to generally better optimize the program. We
additionally have better codegen in our backends for `trapz` than branches to
unconditional traps.

Fixes bytecodealliance#10941
@fitzgen fitzgen requested review from a team as code owners June 9, 2025 17:29
@fitzgen fitzgen requested review from abrown and alexcrichton and removed request for a team June 9, 2025 17:29
@alexcrichton
Copy link
Member

Could you detail more why this is part of legalization and not a separate function/pass? It seems a bit odd to me to have this be intertwined with legalization as it's basically not realized to legalization at all and is more of an optimization pass. Was it a compile-speed-related concern?

Also, do you know why so many tests are changing? Is it also due to the nature of the first commit where it's switching to a backwards walk from a forwards walk which renumbers legalization-produced things which GVN might pick differently between now?

We additionally have better codegen in our backends for trapz than branches to unconditional traps.

FWIW with cold blocks I don't think this is true, I believe if the trapping block is cold the codegen should basically be the same. That being said the GVN point carries this PR alone IMO in terms of motivation.

@fitzgen
Copy link
Member Author

fitzgen commented Jun 9, 2025

Could you detail more why this is part of legalization and not a separate function/pass? It seems a bit odd to me to have this be intertwined with legalization as it's basically not realized to legalization at all and is more of an optimization pass. Was it a compile-speed-related concern?

Yes, I did not want to introduce another IR traversal, and would consider that a non-starter for solving this issue. We have in general been trying to remove passes (eg we've talked a lot about how to fuse phi-removal into the egraph pass) rather than add them.

You can think of legalization as a type of canonicalization, and this PR is canonicalizing conditional-branch-to-unconditional-trap into conditional-trap, if that helps.

Also, do you know why so many tests are changing? Is it also due to the nature of the first commit where it's switching to a backwards walk from a forwards walk which renumbers legalization-produced things which GVN might pick differently between now?

Yes, GVN is a forwards pass will choose the first value it sees as the canonical version. This used to be the legalized value with the lowest number because legalization was also a forwards pass so values at the start of the function would be legalized before those at the end. After the change to legalize in reverse, the legalized values at the end of the function are lower and at the start of the function are higher. So even if the IR is the "same" except for value numbers, the value numbers do change in the golden output.

FWIW with cold blocks I don't think this is true, I believe if the trapping block is cold the codegen should basically be the same.

We don't mark any Wasm blocks cold during translation today, so we won't actually get to that scenario. But maybe we should start making translation smarter and marking blocks cold based on heuristics at some point.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Sounds reasonable to me 👍

@fitzgen fitzgen added this pull request to the merge queue Jun 9, 2025
Merged via the queue into bytecodealliance:main with commit a80dd80 Jun 9, 2025
41 checks passed
@fitzgen fitzgen deleted the conditional-branch-to-unconditional-trap branch June 9, 2025 20:37
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.

Optimize control-flow-y conditional traps into single CLIF trapnz instructions
2 participants