cfallin opened PR #10709 from cfallin:fix-try-call-with-empty-handlers to bytecodealliance:main:
(Reported by @bjorn3 -- thanks!)
We handle edge splitting for regalloc moves in a way that is designed to split minimally (for copmiler performance and codegen quality): we only split edges that are truly critical, i.e., come from a block with more than one successor and go to a block with more than one predecessor. In all other cases, there is always a place for these moves to go: if a block only has one successor, then edge-moves can go before its jump; and if a block only has one predecessor, then edge-moves can go at the beginning of that block (before the blockparams' parallel-move). The former case -- before the branch -- works because of a restriction that regalloc2 imposes: branches with only one target (that is, unconditional branches) cannot have any arguments. Otherwise, those uses would occur after the edge-moves have already shuffled the register state into a state suitable for the next block. Violating this constraint leads to a panic.
With ordinary unconditional branches, this is no problem. With
try_calls with at least one handler listed, this is also no problem: such a terminator is seen as a branch with (at least) two targets by regalloc, so no edge-moves are placed before it, so it's allowed to have arguments, such as the arguments to the call itself. However, atry_callwith no handler clauses, though pathological, appears to regalloc as an unconditional branch and so should not have any arguments. The included test-case triggers this issue with such atry_calltogether with a normal-return target branch that has more than one incoming edge, forcing the location for the moves into thetry_call's block. (The lack of actual edge-moves doesn't matter -- RA2 performs the check on the IR restriction first.) The result is a panic at compile time.This PR fixes the issue by extending a similar fix for
br_tables (which can trigger a very similar bug if they have only the default case, i.e., one target) totry_call{,_indirect}as well: the lowered-block order computation, where edge-splits are determined, pretends that they always have at least two successors. This ensures that edges will be split as necessary, satisfying the no-arguments-to-unconditional-terminators restriction.<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
cfallin requested fitzgen for a review on PR #10709.
cfallin requested wasmtime-compiler-reviewers for a review on PR #10709.
cfallin updated PR #10709.
cfallin edited PR #10709:
(Reported by @bjorn3 -- thanks!)
We handle edge splitting for regalloc moves in a way that is designed to split minimally (for compiler performance and codegen quality): we only split edges that are truly critical, i.e., come from a block with more than one successor and go to a block with more than one predecessor. In all other cases, there is always a place for these moves to go: if a block only has one successor, then edge-moves can go before its jump; and if a block only has one predecessor, then edge-moves can go at the beginning of that block (before the blockparams' parallel-move). The former case -- before the branch -- works because of a restriction that regalloc2 imposes: branches with only one target (that is, unconditional branches) cannot have any arguments. Otherwise, those uses would occur after the edge-moves have already shuffled the register state into a state suitable for the next block. Violating this constraint leads to a panic.
With ordinary unconditional branches, this is no problem. With
try_calls with at least one handler listed, this is also no problem: such a terminator is seen as a branch with (at least) two targets by regalloc, so no edge-moves are placed before it, so it's allowed to have arguments, such as the arguments to the call itself. However, atry_callwith no handler clauses, though pathological, appears to regalloc as an unconditional branch and so should not have any arguments. The included test-case triggers this issue with such atry_calltogether with a normal-return target branch that has more than one incoming edge, forcing the location for the moves into thetry_call's block. (The lack of actual edge-moves doesn't matter -- RA2 performs the check on the IR restriction first.) The result is a panic at compile time.This PR fixes the issue by extending a similar fix for
br_tables (which can trigger a very similar bug if they have only the default case, i.e., one target) totry_call{,_indirect}as well: the lowered-block order computation, where edge-splits are determined, pretends that they always have at least two successors. This ensures that edges will be split as necessary, satisfying the no-arguments-to-unconditional-terminators restriction.<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
bjorn3 commented on PR #10709:
Please remove the @ mention from the commit message and PR description to avoid pinging me every time someone pushes it to their repo.
cfallin updated PR #10709.
cfallin edited PR #10709:
(Reported by bjorn3 -- thanks!)
We handle edge splitting for regalloc moves in a way that is designed to split minimally (for compiler performance and codegen quality): we only split edges that are truly critical, i.e., come from a block with more than one successor and go to a block with more than one predecessor. In all other cases, there is always a place for these moves to go: if a block only has one successor, then edge-moves can go before its jump; and if a block only has one predecessor, then edge-moves can go at the beginning of that block (before the blockparams' parallel-move). The former case -- before the branch -- works because of a restriction that regalloc2 imposes: branches with only one target (that is, unconditional branches) cannot have any arguments. Otherwise, those uses would occur after the edge-moves have already shuffled the register state into a state suitable for the next block. Violating this constraint leads to a panic.
With ordinary unconditional branches, this is no problem. With
try_calls with at least one handler listed, this is also no problem: such a terminator is seen as a branch with (at least) two targets by regalloc, so no edge-moves are placed before it, so it's allowed to have arguments, such as the arguments to the call itself. However, atry_callwith no handler clauses, though pathological, appears to regalloc as an unconditional branch and so should not have any arguments. The included test-case triggers this issue with such atry_calltogether with a normal-return target branch that has more than one incoming edge, forcing the location for the moves into thetry_call's block. (The lack of actual edge-moves doesn't matter -- RA2 performs the check on the IR restriction first.) The result is a panic at compile time.This PR fixes the issue by extending a similar fix for
br_tables (which can trigger a very similar bug if they have only the default case, i.e., one target) totry_call{,_indirect}as well: the lowered-block order computation, where edge-splits are determined, pretends that they always have at least two successors. This ensures that edges will be split as necessary, satisfying the no-arguments-to-unconditional-terminators restriction.<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
cfallin commented on PR #10709:
Sorry about that -- amended the commit and edited the description.
fitzgen submitted PR review:
Thanks for the detailed description! Made understanding this easy :+1:
fitzgen merged PR #10709.
Last updated: Dec 06 2025 at 07:03 UTC