From 874d13f9a8c7495a16802cf5086bbae9e868e6f2 Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Mon, 17 Aug 2026 18:29:01 +0200 Subject: [PATCH] Compare all 64 bits in the relational branches BEQ and BNE already compare the full GPR, but BLEZ, BGTZ, BLTZ and BGEZ (and their likely/and-link variants) were emitted against the low word only. The R5900 compares the whole 64-bit register, so any value whose upper half is significant takes the wrong branch. Compilers reach these opcodes through the dsll32/dsra32 sign-extension idiom, which leaves a canonical value and hides the bug; code that keeps a genuine 64-bit quantity in the register does not. --- ps2xRecomp/src/lib/control_flow_emitter.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ps2xRecomp/src/lib/control_flow_emitter.cpp b/ps2xRecomp/src/lib/control_flow_emitter.cpp index 4bf8dc550..ce3020675 100644 --- a/ps2xRecomp/src/lib/control_flow_emitter.cpp +++ b/ps2xRecomp/src/lib/control_flow_emitter.cpp @@ -404,12 +404,16 @@ namespace ps2recomp case OPCODE_BNE: case OPCODE_BNEL: return fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rsReg, rtReg); + // The R5900 compares the full 64-bit GPR for the relational branches, as it + // already does for BEQ/BNE above. Comparing only the low word takes the wrong + // branch whenever the upper half is significant, which happens with the + // dsll32/dsra32 sign-extension idiom compilers emit ahead of these opcodes. case OPCODE_BLEZ: case OPCODE_BLEZL: - return fmt::format("GPR_S32(ctx, {}) <= 0", rsReg); + return fmt::format("GPR_S64(ctx, {}) <= 0", rsReg); case OPCODE_BGTZ: case OPCODE_BGTZL: - return fmt::format("GPR_S32(ctx, {}) > 0", rsReg); + return fmt::format("GPR_S64(ctx, {}) > 0", rsReg); case OPCODE_REGIMM: switch (m_branchInst.rt) { @@ -417,12 +421,12 @@ namespace ps2recomp case REGIMM_BLTZL: case REGIMM_BLTZAL: case REGIMM_BLTZALL: - return fmt::format("GPR_S32(ctx, {}) < 0", rsReg); + return fmt::format("GPR_S64(ctx, {}) < 0", rsReg); case REGIMM_BGEZ: case REGIMM_BGEZL: case REGIMM_BGEZAL: case REGIMM_BGEZALL: - return fmt::format("GPR_S32(ctx, {}) >= 0", rsReg); + return fmt::format("GPR_S64(ctx, {}) >= 0", rsReg); default: return "false"; }