fix(shaders): a loop that leaves by predicated EXITs ends at its closing self-branch (the last program that did not lower) #1810
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1810
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/lower-end-at-closing-branch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The last of OoT's programs that fail to translate (
f24a3ebf686232fb, a compute program with matrix multiplies) is refused as "native program has no unconditional EXIT". It's a loop that leaves only through@P0 EXIT, so it has no unconditional EXIT. It ends the way NVIDIA's compiler closes such programs: the loop's unconditionalBRAback, then aBRAto itself that nothing reaches.The end of the program (instructions 1808-1809, then NOP padding to 1823; its five EXITs at 646, 956, 1244, 1525 and 1528 are all
@P0):Change (
lower_to_maxwell.h, BRA): the closing self-branch already ended a program after a jump table (computed_branch). It now also ends one when the instruction before it is an unconditional BRA or EXIT, so nothing can fall into it, and the program makes no calls.finishstill refuses if any branch points past the end. A self-branch that can be fallen into is still not an end, and programs that end at an unconditional EXIT end there as before.Tests
tests/test_lower_end_at_closing_branch.py:@P0 EXIT; BRA 0; BRA selflowers and ends at the self-branch (refused onmain). After a NOP, the self-branch is no end.@P0 EXIT; EXIT; BRA selfends at the EXIT as before.run_all.py: all pass exceptnx2/test_nup_stand_in.py, red onmain(fixed by #1790).🤖 Generated with Claude Code
Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!
Nice small fix, and the test pins the case down well. I traced the rule. With no calls and no jump table, every transfer in the program is a relocation, and the self-branch is emitted into the output too. So a thread that somehow reached it would spin in both versions, and ending the capture there doesn't change what the program does. Programs that end at an unconditional EXIT still finish there first, so the new path only fires when that EXIT had a forward branch pending, or when the program has no unconditional EXIT at all (this one).
On #1830: it changes the same spot. Its rule is broader (any unconditional self-branch, once no relocation or call target lies past it), and it also looks sound. I'd land this one, though: it's focused, has a test, and merges cleanly, while #1830 is a large mixed branch that conflicts with main and still has blocking issues. #1830 should drop its copy when it rebases.
One thing worth taking from #1830 (non-blocking, see inline): check for pending forward branches before finishing. Without that check, a mid-program
EXIT; BRA selfwith code after it that a branch reaches now fails infinish, where before it carried on and lowered. I don't know of a program shaped like that (NVIDIA puts the self-branch at the very end), so I wouldn't hold the PR for it.Nit: the third test case only checks that
{@P0 EXIT, EXIT, BRA self}lowers.CHECK(r.source_size == 2 * 16)would show that it ends at the EXIT, as the comment says.CI is still pending. The branch is 33 commits behind main but merges cleanly. If CI shows main's known failures, a merge of main should clear them.
@ -3412,1 +3416,4 @@};const bool after_jump = index > 0 && call_targets.empty() && unconditional_jump(code[index - 1]);if ((computed_branch || after_jump) && target == s64{index} * 16 && branch_predicate == (u64{7} << 16))return finish(index); // the closing self-branchSuggestion (non-blocking):
finishfails when any relocation targets pastindex, so a self-branch that isn't the real end now refuses the program instead of carrying on. Say@P1 BRA L; ...; EXIT; BRA self; L: ...; EXIT: main lowers that, and this PR fails it with "native branch or call leaves captured program". #1830's guard avoids that:(I left the jump-table path as it is.)