Skip to content

test(recomp): per-instruction tests for R5900 general instructions - #283

Open
luisbraganca wants to merge 1 commit into
ran-j:mainfrom
luisbraganca:feature/r5900-general-instruction-tests
Open

luisbraganca wants to merge 1 commit into
ran-j:mainfrom
luisbraganca:feature/r5900-general-instruction-tests

Conversation

@luisbraganca

@luisbraganca luisbraganca commented Oct 10, 2026 •

Copy link
Copy Markdown

Summary

Adds ps2xTest/src/r5900_general_instruction_tests.cpp: three ps2x_tests suites covering every MipsOpcodes, SpecialFunctions and RegimmFunctions encoding (46 cases), and fixes the semantic deviations those tests exposed in the translators and the control-flow emitter.

Closes #160 (the general-instruction part of #147).

How the tests work

Each case decodes a real 32-bit instruction word with R5900Decoder, translates it with CodeGenerator::translateInstruction and pins the emitted C++ verbatim (modulo whitespace). The pinned C++ is then executed inside the test against an R5900Context and a guest RAM buffer through ps2_runtime_macros.h, i.e. through the exact macros a recompiled game compiles against. So a case fails both when the recompiler emits something different and when that code does the wrong thing at runtime (PS2X_EXEC_EMITTED). Branches and jumps are materialised by the function emitter, so those cases emit a six-instruction function around the instruction under test and check the condition expression (also executed, via PS2X_BRANCH_TAKEN), delay-slot placement, link writes and dispatch kinds.

Covered edge behaviour includes: integer-overflow exceptions for ADD/ADDI/SUB/DADD/DADDI/DSUB (and that rd/rt is left untouched), MIPS division-by-zero and INT32_MIN / -1 results, sign/zero extension of every load width, 64-bit vs 32-bit compare semantics (SLT/SLTI/SLTIU/traps/branches), shift-amount masking (5 and 6 bits), LWL/LWR/LDL/LDR/SWL/SWR/SDL/SDR at every offset against a byte-level reference model (with the LWR upper-word rule), LQ/SQ address alignment, LL/SC reservation protocol, MTSAB/MTSAH, and that bits 64..127 of a GPR survive scalar writes.

Three "manifest" cases walk all 64 opcodes, 64 SPECIAL function codes and 32 REGIMM codes and check that exactly the EE-reserved encodings (COP3, LWC2/SWC2, LLD/SCD, LDC1/SDC1, the DMULT family, holes) are rejected with a throw, so a translator regression that silently drops an instruction is caught.

Fixes found by the tests

Hardware behaviour cross-checked against the EE Core manual and PCSX2's interpreter (R5900OpcodeImpl.cpp / Interpreter.cpp):

  • BLEZ/BGTZ/BLTZ/BGEZ (+likely, +link) compared only the low 32 bits (GPR_S32); the R5900 tests the full 64-bit register (cpuRegs.GPR.r[rs].SD[0] in PCSX2). Now GPR_S64.
  • BLTZALL/BGEZALL wrote $ra only when taken; every "and link" branch links unconditionally (MIPS GPR[31] ← PC + 8 happens in the issue stage, _SetLink(31) precedes the condition in PCSX2).
  • MOVZ/MOVN copied the whole 128-bit register (SET_GPR_VEC) and therefore clobbered bits 64..127 of rd; they are 64-bit moves (UD[0] in PCSX2). Now SET_GPR_U64.
  • LQ/SQ used the unmasked effective address; the hardware silently ignores the low four bits (addr & ~0xf in PCSX2). LQC2/SQC2 are left as they were (PCSX2 does not mask them either).
  • Signed overflow UB: ADD32_OV/SUB32_OV, DADDI, DADD, DSUB computed the overflowing sum in signed arithmetic before testing for overflow, and DADDIU added as int64_t. With LTO Release builds that is undefined behaviour on exactly the inputs the overflow tests use. The sums are now formed in unsigned arithmetic; results are unchanged on every non-overflowing input.

Verification

  • ./build/ps2xTest/ps2x_tests: 483 passed, 0 failed (437 before this change).
  • Built on macOS/Apple Silicon (AppleClang, sse2neon path, -DPS2X_ENABLE_FFMPEG=OFF).
  • Without the translator/emitter fixes, the new suites fail on: BEQ and BNE compare 64 bits…, BEQL…, BLTZ, BGEZ…, BLTZAL…, MOVZ and MOVN…, LQ and SQ… (the pinned strings and the executed semantics both change).

test(recomp): per-instruction tests for R5900 general instructions

Add ps2xTest/src/r5900_general_instruction_tests.cpp: three ps2x_tests suites that cover every MipsOpcodes, SpecialFunctions and RegimmFunctions encoding (46 cases). Each case decodes a real instruction word, pins the C++ the recompiler emits for it, and then executes that same C++ against an R5900Context and guest RAM through ps2_runtime_macros.h, so a case fails both when the emission changes and when the emitted code misbehaves.
Branches and jumps go through the function emitter and check the condition expression, delay-slot placement, link writes and dispatch kinds. Manifest cases walk all opcode and function codes and assert that exactly the EE-reserved encodings are rejected.

Edge behaviour covered: integer-overflow traps (and that rd/rt is left untouched), MIPS division by zero and INT32_MIN / -1, sign and zero extension per load width, 64-bit versus 32-bit compares, shift-amount masking, LWL/LWR/LDL/LDR/SWL/SWR/SDL/SDR at every offset against a byte-level reference model, LQ/SQ alignment, LL/SC, MTSAB/MTSAH, and the preservation of bits 64..127 across scalar writes.

Fix the deviations the tests exposed, cross-checked against the EE Core manual and PCSX2's interpreter:

- BLEZ/BGTZ/BLTZ/BGEZ (and their likely and link forms) compared only the low 32 bits; the R5900 tests the full 64-bit register.
- BLTZALL/BGEZALL wrote $ra only when taken; every "and link" branch links unconditionally.
- MOVZ/MOVN copied the whole 128-bit register and clobbered bits 64..127 of rd; they are 64-bit moves.
- LQ/SQ used the unmasked effective address; the hardware ignores the low four bits. LQC2/SQC2 are left as they were.
- ADD32_OV/SUB32_OV, DADDI, DADD, DSUB and DADDIU formed the overflowing sum in signed arithmetic, which is undefined behaviour on exactly the inputs the overflow tests use (and the Release build uses LTO). The sums are now unsigned; non-overflowing results are unchanged.

ps2x_tests: 483 passed, 0 failed (437 before).

Closes ran-j#160
@luisbraganca

Copy link
Copy Markdown
Author

Hi @ran-j,

Just flagging that this overlaps #285 in control_flow_emitter.cpp, instruction_translator.cpp, special_translator.cpp and the ps2xTest registration files, so it'll need a rebase once your performance branch lands. I'm glad to do that. If it helps, I can also split the five translator semantic fixes out from the test file so the test-only part can land independently.

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.

PS2 General Instruction Unit Tests

1 participant