Skip to content

VU1: JR and JALR read the current VI, not the branch backup - #418

Open
NoahBPeterson wants to merge 1 commit into
ran-j:mainfrom
NoahBPeterson:up/vu1-jr-vi
Open

NoahBPeterson wants to merge 1 commit into
ran-j:mainfrom
NoahBPeterson:up/vu1-jr-vi

Conversation

@NoahBPeterson

Copy link
Copy Markdown

I'm Claude, opening this on behalf of @NoahBPeterson. We're working on a static recomp of True Crime: New York City with PS2Recomp, and this is one of the bugs we found along the way.

What's wrong

When an instruction writes a VI register, the next conditional branch (IBEQ, IBNE, IBLTZ, ...) reads the old value: readBranchVi returns the backup. JR and JALR use the same backup, but on the VU they don't. They read the register as it is now. PCSX2's VU interpreter and microVU both do this.

The difference shows up in computed jumps, e.g. ISUB vi9, vi9, vi8 followed by JR vi9 into the remainder of an unrolled loop: the jump went to the old address.

What changed

  • ps2_vu1_lower.cpp: JR and JALR read m_state.vi[is] directly instead of readBranchVi(is) (2 lines plus a comment).
  • ps2_vu1_tests.cpp: the existing test "JR shares the one-instruction VI branch visibility rule" asserted the old behaviour. It now checks that JR right after an IADDIU to its register branches to the new value.

Testing

  • We found it by replaying recorded VU1 runs from the game through PCSX2's VU1 interpreter and comparing the results. On our branch, every recorded run matches with this fix.
  • ps2x_tests on main (e563698) plus this change: 446/446 on macOS arm64 (Apple clang).
  • With the fix reverted, the updated test fails (445/446).
  • feat: GS performance patch 1 #285 renames variables in these two cases (VIS -> viSource) but keeps readBranchVi. Whichever merges second needs a trivial rebase.

🤖 Generated with Claude Code

JR and JALR used the branch backup of a VI written by the previous
instruction, like the conditional branches. Only the conditional branches
take that backup; JR/JALR read the register as it is now (PCSX2's VU
interpreter and microVU). Computed jumps such as `ISUB vi9, vi9, vi8; JR vi9`
into an unrolled loop's remainder went to the wrong instruction.

Found by a differential run of recorded VU1 programs (from True Crime: New
York City) against PCSX2's VU1 interpreter; with the fix every recorded run
matches on that branch. Test: JR right after an IADDIU to its register
branches to the new value.

(ported from commits d3953af and 8cfede8 on the author's tcnyc branch; the
PCSX2 differential tool itself stays there, as it builds against a PCSX2
source tree)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant