Skip to content

Fix: Keep live ranges inside the program when a branch ends it. - #7

Merged
fjtrujy merged 1 commit into
ps2dev:masterfrom
glampert:fix/trailing-branch-range
Oct 22, 2026
Merged

fjtrujy merged 1 commit into
ps2dev:masterfrom
glampert:fix/trailing-branch-range

Conversation

@glampert

Copy link
Copy Markdown

RegisterAllocator::processBranchState ends each path's stretch of the program at the token after a branch. It does Token& token = *curr; curr++;, and then, for a branch, uses &*curr as the end of the path's trace and (*curr).lineNumber() in its backward-branch check. When the branch is the program's last token, curr is tokens.end(). The ps2gl fixtures hit this: they loop back with b main_loop_lid and have no --exit block.

Dereferencing end() is undefined behaviour. In practice it reads whatever lies past the list's sentinel node, and the ranges of values live across the branch end at that number. --show-reg-alloc on test/fixtures/general.vcl shows two ranges like [271-1842013280], with a different end on each run. On macOS the number is always past every line, so the allocation comes out the same. But what the read finds depends on the standard library and the memory layout. A small value would trip assert( start <= stop ) in Alias::addRange, or give an inverted range in a release build.

This fix ends the stretch at the branch itself when nothing follows it (Token& after = (curr != end) ? *curr : token;).

  • Test: test_trailing_branch_ranges.cpp compiles a program that ends with b and checks that every range --show-reg-alloc prints ends within the program. It fails without this fix.
  • Output: byte-identical across a local corpus of 49 programs (quake2-ps2, Tyra, ps2gl, the examples and some repros). Three of them fail to compile on master and with this fix, with the same errors: two have out-of-range immediates, and Tyra's stapip_cull_td_vu1 runs out of VF registers. On master, 10 of the programs print ranges that end past their last line: the nine general* fixtures and ps2gl's billboard renderer. With the fix, none do.
  • Sanitizers: the tests and the corpus run clean under ASan and UBSan. Neither can see this out-of-bounds read, because it stays inside a valid object.

processBranchState ends each path's stretch of the program at the token
after a branch. When the branch was the program's last token, as in the
ps2gl programs that loop back with b and have no --exit block, that token
was the end of the token list. Dereferencing it read past the list, and
the ranges of values live across the branch ended at whatever number was
there: in general.vcl, two ranges ended near 1.8 billion, a different
number on each run.

On macOS that number is always past every line, so the allocation came
out the same, but the read is undefined behaviour and what it finds
depends on the standard library and the memory layout. End the stretch
at the branch itself when nothing follows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@glampert

glampert commented Oct 21, 2026 •

Copy link
Copy Markdown
Author

Not sure what happened with the MacOS test below, seems to have timed out and I don't think I can rerun it. I am working on a Mac, so the MacOS tests are passing.

The job was not acquired by Runner of type hosted even after multiple attempts
Internal server error. Correlation ID: 07e7c0c2-0980-472e-8c99-88d4430ec455

Cheers!

@fjtrujy
fjtrujy merged commit 3ea8bf5 into ps2dev:master Oct 22, 2026
9 of 10 checks passed
@glampert
glampert deleted the fix/trailing-branch-range branch October 23, 2026 06:03
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.

2 participants