From a0f89eb3d4ffa70a4695fa89cc444bf4743a845b Mon Sep 17 00:00:00 2001 From: drako <98249188+drakolordx7@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:09:17 -0500 Subject: [PATCH] fix(recomp): start VCALLMSR from CMSAR0 instead of indexing vi[] VCALLMSR was generated as uint16_t instr_index = ctx->vi[27] & 0x1FF; but vi[] only holds the 16 VU0 integer registers (uint16_t vi[16] in R5900Context). "27" is the VU0 control register number of CMSAR0 (the $vi27 that CTC2 writes, VU0_CR_CMSAR0), so the generated code read 22 bytes past the end of the array, into neighbouring context fields, and never saw the value the game had stored with CTC2. The CTC2 side already stores it in ctx->vu0_cmsar0. Read ctx->vu0_cmsar0 instead; the instruction has no register operand. Generated code has to be regenerated to pick the fix up. A game that calls VCALLMSR from a helper (CTC2 $vi27, then VCALLMSR) hit this; it had been worked around in the runtime. This fixes it at the source, as also described in issue #262. The new code generator test fails without the change. Made by drakolord and assisted with Claude Code. --- ps2xRecomp/src/lib/vu_translation_helpers.cpp | 21 +++++++++---------- ps2xTest/src/code_generator_tests.cpp | 19 +++++++++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/ps2xRecomp/src/lib/vu_translation_helpers.cpp b/ps2xRecomp/src/lib/vu_translation_helpers.cpp index 509fd07b0..e09cc5ecb 100644 --- a/ps2xRecomp/src/lib/vu_translation_helpers.cpp +++ b/ps2xRecomp/src/lib/vu_translation_helpers.cpp @@ -164,17 +164,16 @@ namespace ps2recomp std::string CodeGenerator::translateVU_VCALLMSR(const Instruction &inst) { - // VCALLMSR calls a VU0 microprogram at address stored in integer register - uint8_t vis_reg_idx = inst.rd; // Source integer register (vis) - - return fmt::format( - "{{ " - " uint16_t instr_index = ctx->vi[{}] & 0x1FF; " // Get instruction index from VI[IS], mask to 9 bits - " uint32_t target_byte_addr = (uint32_t)instr_index << 3; " // Convert to byte address - " ctx->vu0_pc = target_byte_addr; " - " runtime->vu0StartMicroProgram(rdram, ctx, target_byte_addr); " - "}}", - vis_reg_idx); + // VCALLMSR calls a VU0 microprogram at the address held in CMSAR0. CMSAR0 is VU0 control register 27 + // (written by CTC2 $vi27, see VU0_CR_CMSAR0), not one of the 16 VI registers, so it lives in ctx->vu0_cmsar0. + (void)inst; + + return "{ " + " uint16_t instr_index = (uint16_t)(ctx->vu0_cmsar0 & 0x1FF); " // Get instruction index from CMSAR0, mask to 9 bits + " uint32_t target_byte_addr = (uint32_t)instr_index << 3; " // Convert to byte address + " ctx->vu0_pc = target_byte_addr; " + " runtime->vu0StartMicroProgram(rdram, ctx, target_byte_addr); " + "}"; } std::string CodeGenerator::translateVU_VRNEXT(const Instruction &inst) diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 463b6cff1..f2a4303b5 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -2035,6 +2035,25 @@ void register_code_generator_tests() "VRXOR should keep the LFSR comment on its own line"); }); + tc.Run("VCALLMSR starts the microprogram from CMSAR0, not a VI array slot", [](TestCase &t) { + CodeGenerator gen({}, {}); + + // VCALLMSR has no register operand; $vi27 in the assembly is CMSAR0 (VU0 control register 27). + Instruction inst{}; + inst.opcode = OPCODE_COP2; + inst.function = VU0_S1_VCALLMSR; + inst.rd = 27; + + const std::string generated = gen.translateVU_VCALLMSR(inst); + printGeneratedCode("VCALLMSR starts the microprogram from CMSAR0, not a VI array slot", generated); + t.IsTrue(generated.find("ctx->vu0_cmsar0") != std::string::npos, + "VCALLMSR should read the start address from CMSAR0"); + t.IsTrue(generated.find("ctx->vi[") == std::string::npos, + "VCALLMSR must not index the 16-entry vi[] array with a control register number"); + t.IsTrue(generated.find("runtime->vu0StartMicroProgram(") != std::string::npos, + "VCALLMSR should still start the VU0 microprogram"); + }); + tc.Run("resolveStubTarget allows leading underscore alias", [](TestCase &t) { t.Equals(PS2Recompiler::resolveStubTarget("_rand"), StubTarget::Stub, "_rand should resolve via rand stub alias");